@datarecce/ui 1.39.0-nightly.20260310 → 1.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AuthModal-B_SYkx-_.js.map +1 -1
- package/dist/advanced.js +1 -1
- package/dist/components.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-BhdiVCZp.js → src-CCK738Ut.js} +6 -6
- package/dist/src-CCK738Ut.js.map +1 -0
- package/dist/utils-m8zHoMN7.js.map +1 -1
- package/package.json +1 -1
- package/dist/src-BhdiVCZp.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthModal-B_SYkx-_.js","names":["useTheme","MuiThemeProvider","Tooltip","changeStatusColors","Tooltip","Tooltip","Tooltip","Tooltip","getLanguageExtension","Popover","Tooltip","ChartJS","Chart","ChartJS","ChartTooltip","Tooltip","SyntaxHighlighter","Alert","Tooltip","Tooltip","toDataGrid","toDataDiffGrid","toValueDiffGrid","BaseRunList","UIRunModal","MuiLink","BaseRunResultPane","ErrorBoundary","SentryErrorBoundary","BaseRunView","useUIRecceActionContext","useCopyToClipboard","useMuiTheme"],"sources":["../src/components/ui/mui-provider.tsx","../src/providers/contexts/CheckContext.tsx","../src/providers/contexts/QueryContext.tsx","../src/hooks/CheckContextAdapter.tsx","../src/components/ui/DataTypeIcon/classifyType.ts","../src/components/ui/DataTypeIcon/icons.tsx","../src/components/ui/DataTypeIcon/tooltipText.ts","../src/components/ui/DataTypeIcon/index.tsx","../src/components/lineage/columns/LineageColumnNode.tsx","../src/components/lineage/styles.tsx","../src/components/lineage/edges/LineageEdge.tsx","../src/components/ui/ChangedOnlyCheckbox.tsx","../src/components/ui/ToggleSwitch.tsx","../src/components/ui/DiffDisplayModeSwitch.tsx","../src/components/ui/DropdownValuesInput.tsx","../src/components/histogram/HistogramDiffForm.tsx","../src/components/lineage/legend/LineageLegend.tsx","../src/components/lineage/nodes/LineageNode.tsx","../src/components/check/CheckActions.tsx","../src/components/check/CheckBreadcrumb.tsx","../src/components/check/CheckCard.tsx","../src/components/check/CheckDescription.tsx","../src/components/check/CheckDetail.tsx","../src/components/check/CheckEmptyState.tsx","../src/components/check/CheckList.tsx","../src/components/lineage/LineageCanvas.tsx","../src/components/lineage/LineageView.tsx","../src/components/check/LineageDiffView.tsx","../src/components/editor/CodeEditor.tsx","../src/components/check/PresetCheckTemplateView.tsx","../src/hooks/useApiConfig.ts","../src/hooks/useAvatar.ts","../src/components/check/timeline/CommentInput.tsx","../src/components/check/timeline/TimelineEvent.tsx","../src/components/check/utils.ts","../src/components/run/RunStatusBadge.tsx","../src/components/run/RunList.tsx","../src/components/run/RunProgress.tsx","../src/components/run/RunToolbar.tsx","../src/components/data/HistogramChart.tsx","../src/components/data/ScreenshotDataGrid.tsx","../src/components/data/TopKBarChart.tsx","../src/components/editor/DiffEditor.tsx","../src/components/ui/EmptyState.tsx","../src/components/ui/ExternalLinkConfirmDialog.tsx","../src/components/ui/MarkdownContent.tsx","../src/components/ui/ScreenshotBox.tsx","../src/components/ui/SplitPane.tsx","../src/components/ui/Split.tsx","../src/components/result/createResultView.tsx","../src/primitives.ts","../src/components/histogram/HistogramResultView.tsx","../src/components/schema/ColumnNameCell.tsx","../src/components/ui/dataGrid/schemaCells.tsx","../src/lib/dataGrid/generators/toSchemaDataGrid.ts","../src/components/ui/dataGrid/valueDiffCells.tsx","../src/components/ui/dataGrid/generators/toValueDataGrid.ts","../src/components/ui/dataGrid/dataGridFactory.tsx","../src/components/profile/ProfileDiffForm.tsx","../src/components/profile/ProfileResultView.tsx","../src/components/query/QueryDiffResultView.tsx","../src/components/query/QueryResultView.tsx","../src/components/rowcount/RowCountResultView.tsx","../src/components/top-k/TopKDiffForm.tsx","../src/components/top-k/TopKDiffResultView.tsx","../src/components/valuediff/ValueDiffDetailResultView.tsx","../src/components/valuediff/ValueDiffForm.tsx","../src/components/valuediff/ValueDiffResultView.tsx","../src/components/run/registry.ts","../src/components/run/RunListOss.tsx","../src/components/run/RunModal.tsx","../src/components/run/RunModalOss.tsx","../src/components/run/RunView.tsx","../src/components/run/RunResultPane.tsx","../src/components/onboarding-guide/Notification.tsx","../src/components/query/SqlEditor.tsx","../src/components/run/RunResultPaneOss.tsx","../src/components/errorboundary/ErrorBoundary.tsx","../src/components/run/RunViewOss.tsx","../src/hooks/useMultiNodesAction.ts","../src/hooks/useValueDiffAlertDialog.tsx","../src/components/lineage/ServerDisconnectedModalContent.tsx","../src/hooks/LineageGraphAdapter.tsx","../src/hooks/QueryContextAdapter.tsx","../src/hooks/RecceActionAdapter.tsx","../src/hooks/RecceShareStateContext.tsx","../src/hooks/RecceContextProvider.tsx","../src/hooks/ScreenShot.tsx","../src/hooks/useCheckEvents.ts","../src/hooks/useCountdownToast.tsx","../src/hooks/useCSVExport.ts","../src/hooks/useFeedbackCollectionToast.tsx","../src/hooks/useGuideToast.tsx","../src/hooks/useModelColumns.tsx","../src/hooks/useRun.tsx","../src/hooks/useThemeColors.ts","../src/lib/api/connectToCloud.ts","../src/components/app/AuthModal.tsx"],"sourcesContent":["\"use client\";\n\nimport CssBaseline from \"@mui/material/CssBaseline\";\nimport { ThemeProvider as MuiThemeProvider } from \"@mui/material/styles\";\nimport { useTheme } from \"next-themes\";\nimport { type ReactNode, useEffect } from \"react\";\nimport { theme } from \"../../theme\";\n\ninterface MuiProviderProps {\n children: ReactNode;\n /**\n * Force a specific theme mode. If not provided, follows system/user preference.\n */\n forcedTheme?: \"light\" | \"dark\";\n /**\n * Whether to include MUI's CssBaseline for consistent baseline styles.\n * Disabled by default to avoid conflicts with Chakra/Tailwind during migration.\n */\n enableCssBaseline?: boolean;\n}\n\n/**\n * MUI Theme Provider for Recce\n *\n * This provider integrates MUI theming with the existing next-themes\n * color mode system used by Chakra UI. MUI 7 CSS Variables mode is used,\n * which responds to the `.dark` class on document.documentElement.\n *\n * Usage:\n * ```tsx\n * <MuiProvider>\n * <MuiButton>Click me</MuiButton>\n * </MuiProvider>\n * ```\n */\nexport function MuiProvider({\n children,\n forcedTheme,\n enableCssBaseline = false,\n}: MuiProviderProps) {\n const { resolvedTheme } = useTheme();\n\n // Toggle .dark class on document.documentElement for CSS Variables mode\n useEffect(() => {\n const mode = forcedTheme ?? resolvedTheme;\n if (mode === \"dark\") {\n document.documentElement.classList.add(\"dark\");\n } else {\n document.documentElement.classList.remove(\"dark\");\n }\n }, [forcedTheme, resolvedTheme]);\n\n // Use single theme - CSS Variables mode handles light/dark via .dark class\n return (\n <MuiThemeProvider theme={theme}>\n {enableCssBaseline && <CssBaseline />}\n {children}\n </MuiThemeProvider>\n );\n}\n\nexport default MuiProvider;\n","\"use client\";\n\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\nexport interface Check {\n check_id: string;\n name: string;\n type: string;\n description?: string;\n is_checked?: boolean;\n}\n\nexport interface CheckContextType {\n checks: Check[];\n isLoading: boolean;\n error?: string;\n selectedCheckId?: string;\n onSelectCheck?: (checkId: string) => void;\n onCreateCheck?: (check: Partial<Check>) => Promise<Check>;\n onUpdateCheck?: (checkId: string, updates: Partial<Check>) => Promise<Check>;\n onDeleteCheck?: (checkId: string) => Promise<void>;\n onReorderChecks?: (sourceIndex: number, destIndex: number) => Promise<void>;\n refetchChecks?: () => void;\n\n // OSS aliases for backward compatibility\n /** @remarks Alias of selectedCheckId (OSS backward compatibility). */\n latestSelectedCheckId?: string;\n /** @remarks Alias of onSelectCheck (OSS backward compatibility). */\n setLatestSelectedCheckId?: (checkId: string) => void;\n}\n\nconst defaultContext: CheckContextType = {\n checks: [],\n isLoading: false,\n};\n\nconst CheckContext = createContext<CheckContextType>(defaultContext);\nCheckContext.displayName = \"RecceCheckContext\";\n\nexport interface CheckProviderProps {\n children: ReactNode;\n checks?: Check[];\n isLoading?: boolean;\n error?: string;\n selectedCheckId?: string;\n onSelectCheck?: (checkId: string) => void;\n onCreateCheck?: (check: Partial<Check>) => Promise<Check>;\n onUpdateCheck?: (checkId: string, updates: Partial<Check>) => Promise<Check>;\n onDeleteCheck?: (checkId: string) => Promise<void>;\n onReorderChecks?: (sourceIndex: number, destIndex: number) => Promise<void>;\n refetchChecks?: () => void;\n\n // OSS aliases for backward compatibility (prefer canonical names above)\n /** @remarks Alias of selectedCheckId (OSS backward compatibility). */\n latestSelectedCheckId?: string;\n /** @remarks Alias of onSelectCheck (OSS backward compatibility). */\n setLatestSelectedCheckId?: (checkId: string) => void;\n}\n\nexport function CheckProvider({\n children,\n checks = [],\n isLoading = false,\n error,\n selectedCheckId,\n onSelectCheck,\n onCreateCheck,\n onUpdateCheck,\n onDeleteCheck,\n onReorderChecks,\n refetchChecks,\n // OSS aliases (canonical props take precedence)\n latestSelectedCheckId,\n setLatestSelectedCheckId,\n}: CheckProviderProps) {\n // Resolve values: canonical props take precedence over OSS aliases\n const resolvedSelectedCheckId = selectedCheckId ?? latestSelectedCheckId;\n const resolvedOnSelectCheck = onSelectCheck ?? setLatestSelectedCheckId;\n\n const contextValue = useMemo<CheckContextType>(\n () => ({\n checks,\n isLoading,\n error,\n // Canonical properties\n selectedCheckId: resolvedSelectedCheckId,\n onSelectCheck: resolvedOnSelectCheck,\n onCreateCheck,\n onUpdateCheck,\n onDeleteCheck,\n onReorderChecks,\n refetchChecks,\n // OSS aliases (point to same resolved values)\n latestSelectedCheckId: resolvedSelectedCheckId,\n setLatestSelectedCheckId: resolvedOnSelectCheck,\n }),\n [\n checks,\n isLoading,\n error,\n resolvedSelectedCheckId,\n resolvedOnSelectCheck,\n onCreateCheck,\n onUpdateCheck,\n onDeleteCheck,\n onReorderChecks,\n refetchChecks,\n ],\n );\n\n return (\n <CheckContext.Provider value={contextValue}>\n {children}\n </CheckContext.Provider>\n );\n}\n\nexport function useCheckContext(): CheckContextType {\n return useContext(CheckContext);\n}\n","\"use client\";\n\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\nexport interface QueryResult {\n columns: string[];\n data: Record<string, unknown>[];\n rowCount: number;\n}\n\nexport interface QueryContextType {\n // --- @datarecce/ui execution state ---\n sql: string;\n isExecuting: boolean;\n error?: string;\n baseResult?: QueryResult;\n currentResult?: QueryResult;\n onSqlChange?: (sql: string) => void;\n onExecute?: (sql: string) => Promise<void>;\n onCancel?: () => void;\n\n // --- OSS input state (merged for backward compatibility) ---\n /** @remarks Alias of sql (OSS backward compatibility). */\n sqlQuery?: string;\n /** @remarks Alias of onSqlChange (OSS backward compatibility). */\n setSqlQuery?: (sql: string) => void;\n /** Primary key columns for diff matching */\n primaryKeys?: string[];\n /** Setter for primary keys */\n setPrimaryKeys?: (pks: string[] | undefined) => void;\n /** Whether using custom SQL queries vs model-generated */\n isCustomQueries?: boolean;\n /** Setter for isCustomQueries */\n setCustomQueries?: (isCustom: boolean) => void;\n /** Base SQL query for diff comparison */\n baseSqlQuery?: string;\n /** Setter for base SQL query */\n setBaseSqlQuery?: (sql: string) => void;\n}\n\nconst defaultContext: QueryContextType = {\n sql: \"\",\n isExecuting: false,\n};\n\nconst QueryContext = createContext<QueryContextType>(defaultContext);\nQueryContext.displayName = \"RecceQueryContext\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n // --- @datarecce/ui execution state ---\n sql?: string;\n isExecuting?: boolean;\n error?: string;\n baseResult?: QueryResult;\n currentResult?: QueryResult;\n onSqlChange?: (sql: string) => void;\n onExecute?: (sql: string) => Promise<void>;\n onCancel?: () => void;\n\n // --- OSS input state ---\n sqlQuery?: string;\n setSqlQuery?: (sql: string) => void;\n primaryKeys?: string[];\n setPrimaryKeys?: (pks: string[] | undefined) => void;\n isCustomQueries?: boolean;\n setCustomQueries?: (isCustom: boolean) => void;\n baseSqlQuery?: string;\n setBaseSqlQuery?: (sql: string) => void;\n}\n\nexport function QueryProvider({\n children,\n sql = \"\",\n isExecuting = false,\n error,\n baseResult,\n currentResult,\n onSqlChange,\n onExecute,\n onCancel,\n // OSS fields\n sqlQuery,\n setSqlQuery,\n primaryKeys,\n setPrimaryKeys,\n isCustomQueries,\n setCustomQueries,\n baseSqlQuery,\n setBaseSqlQuery,\n}: QueryProviderProps) {\n const contextValue = useMemo<QueryContextType>(\n () => ({\n sql,\n isExecuting,\n error,\n baseResult,\n currentResult,\n onSqlChange,\n onExecute,\n onCancel,\n // OSS fields\n sqlQuery,\n setSqlQuery,\n primaryKeys,\n setPrimaryKeys,\n isCustomQueries,\n setCustomQueries,\n baseSqlQuery,\n setBaseSqlQuery,\n }),\n [\n sql,\n isExecuting,\n error,\n baseResult,\n currentResult,\n onSqlChange,\n onExecute,\n onCancel,\n sqlQuery,\n setSqlQuery,\n primaryKeys,\n setPrimaryKeys,\n isCustomQueries,\n setCustomQueries,\n baseSqlQuery,\n setBaseSqlQuery,\n ],\n );\n\n return (\n <QueryContext.Provider value={contextValue}>\n {children}\n </QueryContext.Provider>\n );\n}\n\nexport function useQueryContext(): QueryContextType {\n return useContext(QueryContext);\n}\n","\"use client\";\n\nimport { type ReactNode, useState } from \"react\";\nimport { CheckProvider, useCheckContext } from \"../providers\";\n\ninterface CheckContextAdapterProps {\n children: ReactNode;\n}\n\n/**\n * OSS-compatible CheckContext type with required fields.\n * The @datarecce/ui CheckContextType has optional OSS fields,\n * but OSS components expect them to be defined.\n */\nexport interface OSSCheckContext {\n latestSelectedCheckId: string;\n setLatestSelectedCheckId: (checkId: string) => void;\n}\n\n/**\n * CheckContextAdapter bridges OSS with @datarecce/ui's CheckProvider.\n *\n * The OSS RecceCheckContext was very simple - just selection state:\n * - latestSelectedCheckId: string\n * - setLatestSelectedCheckId: (checkId: string) => void\n *\n * This adapter manages internal state and provides the OSS interface\n * through the @datarecce/ui CheckProvider.\n */\nexport function CheckContextAdapter({ children }: CheckContextAdapterProps) {\n const [selectedCheckId, setSelectedCheckId] = useState<string>(\"\");\n\n return (\n <CheckProvider\n selectedCheckId={selectedCheckId}\n onSelectCheck={setSelectedCheckId}\n latestSelectedCheckId={selectedCheckId}\n setLatestSelectedCheckId={setSelectedCheckId}\n >\n {children}\n </CheckProvider>\n );\n}\n\n// Note: Check, CheckContextType, CheckProviderProps are now imported directly from @datarecce/ui/providers\n// This adapter only exports CheckContextAdapter, useRecceCheckContext, and OSSCheckContext type\n\n// No-op fallback for when hook is used outside provider\nconst noopSetCheckId = () => {\n // Intentionally empty - fallback when used outside CheckContextAdapter\n};\n\n/**\n * OSS-compatible hook that returns the check context with guaranteed non-optional fields.\n * This wraps @datarecce/ui's useCheckContext and provides type safety for OSS components.\n */\nexport function useRecceCheckContext(): OSSCheckContext {\n const ctx = useCheckContext();\n\n // Return OSS-compatible interface with guaranteed values\n // The CheckContextAdapter ensures these are always set\n return {\n latestSelectedCheckId: ctx.latestSelectedCheckId ?? \"\",\n setLatestSelectedCheckId: ctx.setLatestSelectedCheckId ?? noopSetCheckId,\n };\n}\n\n// Note: useCheckContext is now imported directly from @datarecce/ui/providers\n","export type TypeCategory =\n | \"integer\"\n | \"number\"\n | \"text\"\n | \"boolean\"\n | \"date\"\n | \"datetime\"\n | \"time\"\n | \"binary\"\n | \"json\"\n | \"array\"\n | \"geography\"\n | \"unknown\";\n\nconst CATEGORY_MAP: Record<string, TypeCategory> = {\n // integer\n INTEGER: \"integer\",\n INT: \"integer\",\n BIGINT: \"integer\",\n SMALLINT: \"integer\",\n TINYINT: \"integer\",\n INT64: \"integer\",\n INT32: \"integer\",\n INT16: \"integer\",\n INT8: \"integer\",\n INT4: \"integer\",\n INT2: \"integer\",\n MEDIUMINT: \"integer\",\n SERIAL: \"integer\",\n BIGSERIAL: \"integer\",\n SMALLSERIAL: \"integer\",\n\n // number\n DOUBLE: \"number\",\n FLOAT: \"number\",\n REAL: \"number\",\n NUMERIC: \"number\",\n DECIMAL: \"number\",\n NUMBER: \"number\",\n FLOAT64: \"number\",\n FLOAT32: \"number\",\n \"DOUBLE PRECISION\": \"number\",\n\n // text\n VARCHAR: \"text\",\n TEXT: \"text\",\n STRING: \"text\",\n CHAR: \"text\",\n \"CHARACTER VARYING\": \"text\",\n CHARACTER: \"text\",\n NCHAR: \"text\",\n NVARCHAR: \"text\",\n VARCHAR2: \"text\",\n NVARCHAR2: \"text\",\n CLOB: \"text\",\n NCLOB: \"text\",\n TINYTEXT: \"text\",\n MEDIUMTEXT: \"text\",\n LONGTEXT: \"text\",\n\n // boolean\n BOOLEAN: \"boolean\",\n BOOL: \"boolean\",\n\n // date\n DATE: \"date\",\n\n // datetime\n TIMESTAMP: \"datetime\",\n DATETIME: \"datetime\",\n TIMESTAMP_NTZ: \"datetime\",\n TIMESTAMP_LTZ: \"datetime\",\n TIMESTAMP_TZ: \"datetime\",\n TIMESTAMPTZ: \"datetime\",\n \"TIMESTAMP WITH TIME ZONE\": \"datetime\",\n \"TIMESTAMP WITHOUT TIME ZONE\": \"datetime\",\n \"TIMESTAMP WITH LOCAL TIME ZONE\": \"datetime\",\n DATETIME2: \"datetime\",\n SMALLDATETIME: \"datetime\",\n DATETIMEOFFSET: \"datetime\",\n\n // time\n TIME: \"time\",\n TIMETZ: \"time\",\n \"TIME WITH TIME ZONE\": \"time\",\n \"TIME WITHOUT TIME ZONE\": \"time\",\n\n // binary\n BINARY: \"binary\",\n VARBINARY: \"binary\",\n BYTES: \"binary\",\n BLOB: \"binary\",\n BYTEA: \"binary\",\n TINYBLOB: \"binary\",\n MEDIUMBLOB: \"binary\",\n LONGBLOB: \"binary\",\n\n // json\n JSON: \"json\",\n JSONB: \"json\",\n VARIANT: \"json\",\n OBJECT: \"json\",\n STRUCT: \"json\",\n MAP: \"json\",\n\n // array\n ARRAY: \"array\",\n LIST: \"array\",\n\n // geography\n GEOGRAPHY: \"geography\",\n GEOMETRY: \"geography\",\n POINT: \"geography\",\n LINESTRING: \"geography\",\n POLYGON: \"geography\",\n MULTIPOINT: \"geography\",\n MULTILINESTRING: \"geography\",\n MULTIPOLYGON: \"geography\",\n GEOMETRYCOLLECTION: \"geography\",\n SDO_GEOMETRY: \"geography\",\n};\n\n/**\n * Classifies a raw database type string into a TypeCategory.\n *\n * - Case-insensitive\n * - Strips parenthesized parameters before matching (e.g. VARCHAR(256) -> VARCHAR)\n * - Special case: TINYINT(1) maps to \"boolean\", plain TINYINT maps to \"integer\"\n */\nexport function classifyType(rawType: string): TypeCategory {\n const trimmed = rawType.trim().toUpperCase();\n\n if (!trimmed) {\n return \"unknown\";\n }\n\n // Special case: TINYINT(1) is boolean\n if (/^TINYINT\\s*\\(\\s*1\\s*\\)$/.test(trimmed)) {\n return \"boolean\";\n }\n\n // Strip parenthesized parameters (indexOf avoids regex backtracking)\n const parenIdx = trimmed.indexOf(\"(\");\n const base = parenIdx === -1 ? trimmed : trimmed.slice(0, parenIdx).trimEnd();\n\n return CATEGORY_MAP[base] ?? \"unknown\";\n}\n","import type { CSSProperties } from \"react\";\nimport { useId } from \"react\";\n\ninterface IconProps {\n size?: number;\n style?: CSSProperties;\n className?: string;\n}\n\n/**\n * All icons share a uniform viewBox (30x18) with a rounded-rect border.\n * Content (text or line-art) is drawn inside the box.\n * The box stroke and content fill/stroke all use currentColor.\n */\nconst VB_W = 30;\nconst VB_H = 18;\nconst BOX_RX = 3;\nconst BOX_STROKE = 1.2;\nconst BOX_INSET = 0.6; // half stroke so border sits inside viewBox\n\nfunction BoxedSvg({\n size = 24,\n style,\n className,\n children,\n}: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n viewBox={`0 0 ${VB_W} ${VB_H}`}\n width={size}\n height={(size * VB_H) / VB_W}\n style={style}\n className={className}\n aria-hidden=\"true\"\n >\n <rect\n x={BOX_INSET}\n y={BOX_INSET}\n width={VB_W - BOX_INSET * 2}\n height={VB_H - BOX_INSET * 2}\n rx={BOX_RX}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={BOX_STROKE}\n />\n {children}\n </svg>\n );\n}\n\nfunction TextIcon({\n text,\n size,\n style,\n className,\n}: IconProps & { text: string }) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <text\n x={VB_W / 2}\n y={VB_H / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={10.5}\n fontFamily=\"monospace\"\n fontWeight={500}\n fill=\"currentColor\"\n >\n {text}\n </text>\n </BoxedSvg>\n );\n}\n\nexport function IntegerIcon(props: IconProps) {\n return <TextIcon text=\"123\" {...props} />;\n}\n\nexport function NumberIcon(props: IconProps) {\n return <TextIcon text=\"1.2\" {...props} />;\n}\n\nexport function TextTypeIcon(props: IconProps) {\n return <TextIcon text=\"abc\" {...props} />;\n}\n\nexport function BooleanIcon(props: IconProps) {\n return <TextIcon text=\"T/F\" {...props} />;\n}\n\n/**\n * Two-tone split icon — left half filled with \"0\", right half with \"1\"\n */\nexport function BinaryIcon({ size, style, className }: IconProps) {\n const maskId = useId();\n const x = BOX_INSET;\n const y = BOX_INSET;\n const h = VB_H - BOX_INSET * 2;\n const mid = VB_W / 2;\n\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <mask id={maskId}>\n <rect x={x} y={y} width={mid - x} height={h} fill=\"white\" />\n <text\n x={mid / 2}\n y={VB_H / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={10}\n fontFamily=\"monospace\"\n fontWeight={700}\n fill=\"black\"\n >\n 0\n </text>\n </mask>\n <path\n d={`M${mid} ${y} H${x + BOX_RX} Q${x} ${y} ${x} ${y + BOX_RX} V${y + h - BOX_RX} Q${x} ${y + h} ${x + BOX_RX} ${y + h} H${mid} Z`}\n fill=\"currentColor\"\n mask={`url(#${maskId})`}\n />\n <text\n x={mid + (VB_W - mid) / 2}\n y={VB_H / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={10}\n fontFamily=\"monospace\"\n fontWeight={700}\n fill=\"currentColor\"\n >\n 1\n </text>\n </BoxedSvg>\n );\n}\n\nexport function JsonIcon(props: IconProps) {\n return <TextIcon text=\"{ }\" {...props} />;\n}\n\n/**\n * Square brackets with \"1,2\" text — for ARRAY/LIST types\n */\nexport function ArrayIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1.2}\n strokeLinecap=\"round\"\n >\n {/* Left bracket */}\n <line x1={6} y1={5} x2={4} y2={5} />\n <line x1={4} y1={5} x2={4} y2={13} />\n <line x1={4} y1={13} x2={6} y2={13} />\n {/* Right bracket */}\n <line x1={24} y1={5} x2={26} y2={5} />\n <line x1={26} y1={5} x2={26} y2={13} />\n <line x1={26} y1={13} x2={24} y2={13} />\n </g>\n <text\n x={VB_W / 2}\n y={VB_H / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={9}\n fontFamily=\"monospace\"\n fontWeight={500}\n fill=\"currentColor\"\n >\n 1,2\n </text>\n </BoxedSvg>\n );\n}\n\nexport function UnknownIcon(props: IconProps) {\n return <TextIcon text=\"···\" {...props} />;\n}\n\n/**\n * Calendar icon inside box — for DATE type\n */\nexport function DateIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n transform=\"translate(9, 2.5)\"\n >\n {/* Calendar body */}\n <rect x={0} y={2} width={11} height={9.5} rx={1.2} />\n {/* Top hooks */}\n <line x1={3} y1={0.5} x2={3} y2={3.5} />\n <line x1={8} y1={0.5} x2={8} y2={3.5} />\n {/* Divider */}\n <line x1={0} y1={5.5} x2={11} y2={5.5} />\n </g>\n </BoxedSvg>\n );\n}\n\n/**\n * Calendar + clock icon inside box — for DATETIME/TIMESTAMP types\n */\nexport function DatetimeIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n transform=\"translate(4, 2.5)\"\n >\n {/* Calendar (smaller, left) */}\n <rect x={0} y={2} width={9.5} height={8.5} rx={1.2} />\n <line x1={2.5} y1={0.5} x2={2.5} y2={3.5} />\n <line x1={7} y1={0.5} x2={7} y2={3.5} />\n <line x1={0} y1={5} x2={9.5} y2={5} />\n {/* Clock (small, right) */}\n <circle cx={17} cy={8.5} r={3.2} />\n <line x1={17} y1={6.8} x2={17} y2={8.5} />\n <line x1={17} y1={8.5} x2={18.3} y2={9.5} />\n </g>\n </BoxedSvg>\n );\n}\n\n/**\n * Clock icon inside box — for TIME type\n */\nexport function TimeIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n transform=\"translate(10, 3.5)\"\n >\n <circle cx={5} cy={5.5} r={5} />\n <line x1={5} y1={3} x2={5} y2={5.5} />\n <line x1={5} y1={5.5} x2={7} y2={6.8} />\n </g>\n </BoxedSvg>\n );\n}\n\n/**\n * Map pin (teardrop) icon inside box — for GEOGRAPHY/GEOMETRY types\n */\nexport function GeographyIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path\n d=\"M15 14.5 C15 14.5 10.5 11 10.5 8 A4.5 4.5 0 0 1 19.5 8 C19.5 11 15 14.5 15 14.5 Z\"\n strokeWidth={1.2}\n />\n <circle cx={15} cy={8} r={1.5} fill=\"currentColor\" stroke=\"none\" />\n </g>\n </BoxedSvg>\n );\n}\n","export interface ColumnTooltipInput {\n name: string;\n status?:\n | \"added\"\n | \"removed\"\n | \"type_changed\"\n | \"definition_changed\"\n | \"unchanged\";\n baseType?: string;\n currentType?: string;\n cllAvailable?: boolean;\n}\n\nexport function buildColumnTooltip(input: ColumnTooltipInput): string {\n const { name, status, baseType, currentType, cllAvailable } = input;\n\n let text: string;\n\n switch (status) {\n case \"added\":\n text = currentType ? `${name} added ${currentType}` : `${name} added`;\n break;\n\n case \"removed\":\n // Removed columns never get the CLL suffix\n return `deleted ${name}`;\n\n case \"type_changed\":\n text = `${name}, was ${baseType} now ${currentType}`;\n break;\n\n case \"definition_changed\":\n text = currentType\n ? `${name} ${currentType} changed definition`\n : `${name} changed definition`;\n break;\n\n case \"unchanged\":\n text = currentType ? `${name} ${currentType}` : name;\n break;\n\n default: {\n // No status provided — infer from types\n if (baseType && currentType && baseType !== currentType) {\n text = `${name}, was ${baseType} now ${currentType}`;\n } else if (currentType) {\n text = `${name} ${currentType}`;\n } else {\n text = name;\n }\n break;\n }\n }\n\n if (cllAvailable) {\n text += \" \\u00b7 Click for column lineage\";\n }\n\n return text;\n}\n","import Tooltip from \"@mui/material/Tooltip\";\nimport type { CSSProperties } from \"react\";\nimport type { TypeCategory } from \"./classifyType\";\nimport { classifyType } from \"./classifyType\";\nimport {\n ArrayIcon,\n BinaryIcon,\n BooleanIcon,\n DateIcon,\n DatetimeIcon,\n GeographyIcon,\n IntegerIcon,\n JsonIcon,\n NumberIcon,\n TextTypeIcon,\n TimeIcon,\n UnknownIcon,\n} from \"./icons\";\n\nexport { classifyType };\nexport type { TypeCategory };\nexport type { ColumnTooltipInput } from \"./tooltipText\";\nexport { buildColumnTooltip } from \"./tooltipText\";\n\nconst ICON_MAP: Record<\n TypeCategory,\n React.ComponentType<{\n size?: number;\n style?: CSSProperties;\n className?: string;\n }>\n> = {\n integer: IntegerIcon,\n number: NumberIcon,\n text: TextTypeIcon,\n boolean: BooleanIcon,\n date: DateIcon,\n datetime: DatetimeIcon,\n time: TimeIcon,\n binary: BinaryIcon,\n json: JsonIcon,\n array: ArrayIcon,\n geography: GeographyIcon,\n unknown: UnknownIcon,\n};\n\nexport interface DataTypeIconProps {\n type: string;\n size?: number;\n style?: CSSProperties;\n className?: string;\n disableTooltip?: boolean;\n}\n\nexport function DataTypeIcon({\n type,\n size = 24,\n style,\n className,\n disableTooltip,\n}: DataTypeIconProps) {\n const category = classifyType(type);\n const IconComponent = ICON_MAP[category];\n\n const icon = (\n <span\n data-testid=\"data-type-icon\"\n style={{ display: \"inline-flex\", alignItems: \"center\", lineHeight: 0 }}\n >\n <IconComponent size={size} style={style} className={className} />\n </span>\n );\n\n if (disableTooltip) {\n return icon;\n }\n\n return (\n <Tooltip title={type} placement=\"top\" arrow>\n {icon}\n </Tooltip>\n );\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Chip from \"@mui/material/Chip\";\nimport { Handle, Position } from \"@xyflow/react\";\nimport type { MouseEvent } from \"react\";\nimport { memo, useState } from \"react\";\nimport { DataTypeIcon } from \"../../ui/DataTypeIcon\";\n\n/**\n * Transformation type for column-level lineage\n */\nexport type ColumnTransformationType =\n | \"passthrough\"\n | \"renamed\"\n | \"derived\"\n | \"source\"\n | \"unknown\";\n\n/**\n * Column change status for diff views\n */\nexport type ColumnChangeStatus = \"added\" | \"removed\" | \"modified\";\n\n/**\n * Data structure for a column node\n */\nexport interface LineageColumnNodeData extends Record<string, unknown> {\n /** Column name */\n column: string;\n /** Column data type (e.g., \"VARCHAR\", \"INTEGER\") */\n type?: string;\n /** ID of the parent model/table node */\n nodeId: string;\n /** Transformation type for this column */\n transformationType?: ColumnTransformationType;\n /** Change status for diff views */\n changeStatus?: ColumnChangeStatus;\n /** Whether the column is highlighted */\n isHighlighted?: boolean;\n /** Whether the column is selected/focused */\n isFocused?: boolean;\n}\n\n/**\n * Props for the LineageColumnNode component\n */\nexport interface LineageColumnNodeProps {\n /** Unique node ID */\n id: string;\n /** Node data */\n data: LineageColumnNodeData;\n /** Whether the node is selected */\n selected?: boolean;\n\n // === New props for OSS feature parity ===\n\n /**\n * Whether to show content (used for zoom-level visibility)\n * When false, the node renders nothing (hidden at low zoom levels)\n * @default true\n */\n showContent?: boolean;\n\n /**\n * Whether to show change analysis mode\n * When true and changeStatus exists, shows change status indicator\n * When false, shows transformation type indicator\n * @default false\n */\n showChangeAnalysis?: boolean;\n\n /**\n * Whether to use dark mode styling\n * @default false\n */\n isDark?: boolean;\n\n // === Callbacks ===\n\n /** Callback when column is clicked */\n onColumnClick?: (columnId: string) => void;\n\n /**\n * Callback when context menu is requested (kebab menu click)\n * When provided, shows kebab menu on hover\n */\n onContextMenu?: (event: MouseEvent, columnId: string) => void;\n}\n\n/**\n * Default column height in pixels\n */\nexport const COLUMN_NODE_HEIGHT = 24;\n\n/**\n * Default column width in pixels\n */\nexport const COLUMN_NODE_WIDTH = 280;\n\n/**\n * Colors for change status indicators\n */\nconst changeStatusColors: Record<ColumnChangeStatus, string> = {\n added: \"#22c55e\",\n removed: \"#ef4444\",\n modified: \"#f59e0b\",\n};\n\n/**\n * Colors for transformation type chips\n */\nconst transformationColors: Record<\n ColumnTransformationType,\n { letter: string; color: \"default\" | \"warning\" | \"info\" | \"error\" }\n> = {\n passthrough: { letter: \"P\", color: \"default\" },\n renamed: { letter: \"R\", color: \"warning\" },\n derived: { letter: \"D\", color: \"warning\" },\n source: { letter: \"S\", color: \"info\" },\n unknown: { letter: \"U\", color: \"error\" },\n};\n\n/**\n * KebabMenuIcon - Inline SVG to avoid react-icons dependency\n */\nfunction KebabMenuIcon({ size = 14 }: { size?: number }) {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 16 16\"\n fill=\"currentColor\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle cx=\"8\" cy=\"3\" r=\"1.5\" />\n <circle cx=\"8\" cy=\"8\" r=\"1.5\" />\n <circle cx=\"8\" cy=\"13\" r=\"1.5\" />\n </svg>\n );\n}\n\n/**\n * ChangeStatusIndicator - Shows change status icon\n */\nfunction ChangeStatusIndicator({\n changeStatus,\n}: {\n changeStatus?: ColumnChangeStatus;\n}) {\n if (!changeStatus) {\n return null;\n }\n\n const color = changeStatusColors[changeStatus];\n const symbols: Record<ColumnChangeStatus, string> = {\n added: \"+\",\n removed: \"-\",\n modified: \"~\",\n };\n\n return (\n <Box\n sx={{\n width: 14,\n height: 14,\n borderRadius: \"50%\",\n backgroundColor: color,\n color: \"white\",\n fontSize: 10,\n fontWeight: \"bold\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flexShrink: 0,\n }}\n >\n {symbols[changeStatus]}\n </Box>\n );\n}\n\n/**\n * TransformationIndicator - Shows transformation type chip\n */\nfunction TransformationIndicator({\n transformationType,\n}: {\n transformationType?: ColumnTransformationType;\n}) {\n if (!transformationType) {\n return null;\n }\n\n const config = transformationColors[transformationType];\n\n return (\n <Chip\n label={config.letter}\n size=\"small\"\n color={config.color}\n sx={{\n fontSize: \"8pt\",\n height: 18,\n minWidth: 18,\n \"& .MuiChip-label\": {\n px: 0.5,\n },\n }}\n />\n );\n}\n\n/**\n * LineageColumnNode Component\n *\n * A pure presentation component for rendering individual columns\n * in column-level lineage visualizations using React Flow.\n *\n * @example Basic usage\n * ```tsx\n * import { LineageColumnNode } from '@datarecce/ui/primitives';\n *\n * // Register as a React Flow node type\n * const nodeTypes = {\n * columnNode: LineageColumnNode,\n * };\n *\n * function ColumnLineageGraph() {\n * return (\n * <ReactFlow nodes={columnNodes} edges={edges} nodeTypes={nodeTypes} />\n * );\n * }\n * ```\n *\n * @example Node data structure\n * ```tsx\n * const columnNode = {\n * id: 'users-id',\n * type: 'columnNode',\n * data: {\n * column: 'id',\n * type: 'INTEGER',\n * nodeId: 'users',\n * transformationType: 'passthrough',\n * changeStatus: undefined,\n * isHighlighted: true,\n * },\n * position: { x: 0, y: 0 },\n * };\n * ```\n *\n * @example With change analysis mode\n * ```tsx\n * // In change analysis mode, shows change status instead of transformation type\n * <LineageColumnNode\n * showChangeAnalysis={true}\n * showContent={zoomLevel > 0.3}\n * onContextMenu={(e, columnId) => showMenu(e, columnId)}\n * />\n * ```\n */\nfunction LineageColumnNodeComponent({\n id,\n data,\n showContent = true,\n showChangeAnalysis = false,\n isDark = false,\n onColumnClick,\n onContextMenu,\n}: LineageColumnNodeProps) {\n const {\n column,\n type,\n transformationType,\n changeStatus,\n isHighlighted = true,\n isFocused = false,\n } = data;\n\n const [isHovered, setIsHovered] = useState(false);\n\n // Hide node when showContent is false (low zoom level)\n if (!showContent) {\n return null;\n }\n\n // Determine what indicator to show based on showChangeAnalysis mode\n const shouldShowChangeStatus = showChangeAnalysis && changeStatus;\n\n return (\n <Box\n onClick={() => onColumnClick?.(id)}\n sx={{\n display: \"flex\",\n width: COLUMN_NODE_WIDTH,\n padding: \"0px 10px\",\n border: \"1px solid\",\n borderColor: \"divider\",\n backgroundColor: isFocused\n ? \"action.selected\"\n : isHovered\n ? \"action.hover\"\n : \"background.paper\",\n filter: isHighlighted ? \"none\" : \"opacity(0.2) grayscale(50%)\",\n cursor: \"pointer\",\n transition: \"background-color 0.15s ease\",\n }}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <Box\n sx={{\n display: \"flex\",\n fontSize: \"11px\",\n color: \"text.primary\",\n width: \"100%\",\n gap: \"6px\",\n alignItems: \"center\",\n height: `${COLUMN_NODE_HEIGHT - 1}px`,\n }}\n >\n {/* Status indicator - based on showChangeAnalysis mode */}\n {shouldShowChangeStatus ? (\n <ChangeStatusIndicator changeStatus={changeStatus} />\n ) : (\n <TransformationIndicator transformationType={transformationType} />\n )}\n\n {/* Column name */}\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n flexGrow: 1,\n height: `${COLUMN_NODE_HEIGHT + 1}px`,\n lineHeight: `${COLUMN_NODE_HEIGHT + 1}px`,\n }}\n >\n {column}\n </Box>\n\n {/* Column type or kebab menu */}\n {isHovered && onContextMenu ? (\n <Box\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n cursor: \"pointer\",\n \"&:hover\": { color: \"text.primary\" },\n }}\n onClick={(e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onContextMenu(e, id);\n }}\n data-testid=\"column-kebab-menu\"\n >\n <KebabMenuIcon size={14} />\n </Box>\n ) : (\n type && (\n <DataTypeIcon\n type={type}\n size={16}\n style={{ flexShrink: 0, opacity: 0.7 }}\n />\n )\n )}\n </Box>\n\n {/* Connection handles */}\n <Handle\n type=\"target\"\n position={Position.Left}\n isConnectable={false}\n style={{\n left: 0,\n visibility: \"hidden\",\n }}\n />\n <Handle\n type=\"source\"\n position={Position.Right}\n isConnectable={false}\n style={{\n right: 0,\n visibility: \"hidden\",\n }}\n />\n </Box>\n );\n}\n\nexport const LineageColumnNode = memo(LineageColumnNodeComponent);\nLineageColumnNode.displayName = \"LineageColumnNode\";\n","\"use client\";\n\n/**\n * @file styles.ts\n * @description Styling utilities for lineage components\n *\n * Provides icons, colors, and styling helpers for:\n * - Change status visualization (added, removed, modified)\n * - Resource type icons (model, source, seed, etc.)\n *\n * Source: Ported from OSS js/src/components/lineage/styles.tsx\n */\n\nimport type { ComponentType, SVGProps } from \"react\";\nimport { colors } from \"../../theme/colors\";\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\n/**\n * Change status for diff visualization\n */\nexport type ChangeStatus = \"added\" | \"removed\" | \"modified\" | \"unchanged\";\n\n/**\n * Resource types supported by dbt/lineage\n */\nexport type ResourceType =\n | \"model\"\n | \"source\"\n | \"seed\"\n | \"snapshot\"\n | \"metric\"\n | \"exposure\"\n | \"semantic_model\";\n\n/**\n * Icon component type\n */\nexport type IconComponent = ComponentType<SVGProps<SVGSVGElement>>;\n\n/**\n * Result from getIconForChangeStatus\n */\nexport interface ChangeStatusStyle {\n /** CSS color value for the status */\n color: string;\n /** Hex color value (same as color for compatibility) */\n hexColor: string;\n /** Background color for light/dark mode */\n backgroundColor: string;\n /** Hex background color (same as backgroundColor for compatibility) */\n hexBackgroundColor: string;\n /** Icon component for the status, undefined if no change */\n icon: IconComponent | undefined;\n}\n\n/**\n * Result from getIconForResourceType\n */\nexport interface ResourceTypeStyle {\n /** CSS color value for the resource type */\n color: string;\n /** Icon component for the resource type, undefined if unknown */\n icon: IconComponent | undefined;\n}\n\n// =============================================================================\n// SVG ICON COMPONENTS\n// =============================================================================\n\n/**\n * Plus icon for \"added\" status\n * Based on VSCode's VscDiffAdded\n */\nexport const IconAdded: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 2v12h12V2H2zm6.5 2.5v2.5h2.5v1H8.5v2.5h-1V8H5V7h2.5V4.5h1z\"\n />\n </svg>\n);\n\n/**\n * Minus icon for \"removed\" status\n * Based on VSCode's VscDiffRemoved\n */\nexport const IconRemoved: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 2v12h12V2H2zm3 5.5h6v1H5v-1z\"\n />\n </svg>\n);\n\n/**\n * Tilde icon for \"modified\" status\n * Based on VSCode's VscDiffModified\n */\nexport const IconModified: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 2v12h12V2H2z\" />\n <path d=\"M10.6 8.7c-.1.1-.3.2-.5.2h-.1l-1.4-.3c-.5-.1-1.1-.3-1.8-.3-.6 0-1 .2-1.3.5-.2.2-.2.4-.2.7v.1l-.9.3v-.2c0-.3 0-.6.1-.9.2-.4.5-.8 1-1.1.5-.4 1.2-.5 2-.5h.2l1.5.4h.1c.5.1 1 .3 1.5.3.6 0 .9-.2 1.2-.4.2-.2.3-.5.3-.8v-.3l.8-.3h.1v.3c0 .6-.2 1.2-.6 1.6-.2.2-.3.4-.5.5l-.5.2z\" />\n </svg>\n);\n\n/**\n * Dot icon for \"changed\" status (generic change indicator)\n */\nexport const IconChanged: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z\"\n />\n </svg>\n);\n\n/**\n * Icon for modified with downstream impact indicator\n */\nexport const IconModifiedDownstream: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 2v4h-1v4h1v4h4v1h4v-1h4v-4h1v-4h-1v-4h-4v-1h-4v1h-4z\"\n />\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z\"\n />\n </svg>\n);\n\n// =============================================================================\n// RESOURCE TYPE ICONS (inline SVGs to avoid react-icons dependency)\n// =============================================================================\n\n/**\n * Cube icon for \"model\" resource type\n */\nexport const IconModel: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M239.1 6.3l-208 78c-18.7 7-31.1 25-31.1 45v225.1c0 18.2 10.3 34.8 26.5 42.9l208 104c13.5 6.8 29.4 6.8 42.9 0l208-104c16.3-8.1 26.5-24.8 26.5-42.9V129.3c0-20-12.4-37.9-31.1-44.9l-208-78C262 2.2 250 2.2 239.1 6.3zM256 68.4l192 72v1.1l-192 78-192-78v-1.1l192-72zm32 356V275.5l160-65v133.9l-160 80z\" />\n </svg>\n);\n\n/**\n * Database icon for \"source\" resource type\n */\nexport const IconSource: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 448 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M448 73.143v45.714C448 159.143 347.667 192 224 192S0 159.143 0 118.857V73.143C0 32.857 100.333 0 224 0s224 32.857 224 73.143zM448 176v102.857C448 319.143 347.667 352 224 352S0 319.143 0 278.857V176c48.125 33.143 136.208 48.572 224 48.572S399.874 209.143 448 176zm0 160v102.857C448 479.143 347.667 512 224 512S0 479.143 0 438.857V336c48.125 33.143 136.208 48.572 224 48.572S399.874 369.143 448 336z\" />\n </svg>\n);\n\n/**\n * Seedling icon for \"seed\" resource type\n */\nexport const IconSeed: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M64 96H0c0 123.7 100.3 224 224 224v144c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V320C288 196.3 187.7 96 64 96zm384-64c-84.2 0-157.4 46.5-195.7 115.2 27.7 30.2 48.2 66.9 59 107.6C424 243.1 512 147.9 512 32h-64z\" />\n </svg>\n);\n\n/**\n * Camera icon for \"snapshot\" resource type\n */\nexport const IconSnapshot: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M512 144v288c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48h88l12.3-32.9c7-18.7 24.9-31.1 44.9-31.1h125.5c20 0 37.9 12.4 44.9 31.1L376 96h88c26.5 0 48 21.5 48 48zM376 288c0-66.2-53.8-120-120-120s-120 53.8-120 120 53.8 120 120 120 120-53.8 120-120zm-32 0c0 48.5-39.5 88-88 88s-88-39.5-88-88 39.5-88 88-88 88 39.5 88 88z\" />\n </svg>\n);\n\n/**\n * Chart icon for \"metric\" resource type\n */\nexport const IconMetric: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 448 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M160 80c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48h-32c-26.5 0-48-21.5-48-48V80zM0 272c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48v160c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V272zM368 96h32c26.5 0 48 21.5 48 48v288c0 26.5-21.5 48-48 48h-32c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48z\" />\n </svg>\n);\n\n/**\n * Gauge icon for \"exposure\" resource type\n */\nexport const IconExposure: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm320 96c0-15.9-5.8-30.4-15.3-41.6l76.6-147.4c6.1-11.8 1.5-26.3-10.2-32.4s-26.2-1.5-32.4 10.2L262.1 288.3c-2-.2-4-.3-6.1-.3c-35.3 0-64 28.7-64 64s28.7 64 64 64s64-28.7 64-64z\" />\n </svg>\n);\n\n/**\n * Nodes icon for \"semantic_model\" resource type\n */\nexport const IconSemanticModel: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M418.4 157.9c35.3-8.3 61.6-40 61.6-77.9c0-44.2-35.8-80-80-80c-43.4 0-78.7 34.5-80 77.5L136.2 151.1C121.7 136.8 101.9 128 80 128c-44.2 0-80 35.8-80 80s35.8 80 80 80c12.2 0 23.8-2.7 34.1-7.6L259.7 407.8c-2.4 7.6-3.7 15.8-3.7 24.2c0 44.2 35.8 80 80 80s80-35.8 80-80c0-27.7-14-52.1-35.4-66.4l37.8-207.7zM156.3 232.2c2.2-6.9 3.5-14.2 3.7-21.7l183.8-73.5c3.6 3.5 7.4 6.7 11.6 9.5L317.6 354.1c-5.5 1.3-10.8 3.1-15.8 5.5L156.3 232.2z\" />\n </svg>\n);\n\n// =============================================================================\n// STYLING FUNCTIONS\n// =============================================================================\n\n/**\n * Get icon and colors for a change status\n *\n * @param changeStatus - The change status (added, removed, modified)\n * @param isDark - Whether dark mode is active\n * @returns Object containing color values and icon component\n *\n * @example\n * ```tsx\n * const { color, icon: Icon } = getIconForChangeStatus(\"added\");\n * return <Icon style={{ color }} />;\n * ```\n */\nexport function getIconForChangeStatus(\n changeStatus?: ChangeStatus,\n isDark?: boolean,\n): ChangeStatusStyle {\n if (changeStatus === \"added\") {\n return {\n color: colors.green[500],\n hexColor: colors.green[500],\n backgroundColor: isDark ? colors.green[900] : colors.green[100],\n hexBackgroundColor: isDark ? colors.green[900] : colors.green[100],\n icon: IconAdded,\n };\n }\n\n if (changeStatus === \"removed\") {\n return {\n color: colors.red[500],\n hexColor: colors.red[500],\n backgroundColor: isDark ? colors.red[950] : colors.red[200],\n hexBackgroundColor: isDark ? colors.red[950] : colors.red[200],\n icon: IconRemoved,\n };\n }\n\n if (changeStatus === \"modified\") {\n return {\n color: colors.amber[500],\n hexColor: colors.amber[500],\n backgroundColor: isDark ? colors.amber[900] : colors.amber[100],\n hexBackgroundColor: isDark ? colors.amber[900] : colors.amber[100],\n icon: IconModified,\n };\n }\n\n // Default: no change\n return {\n color: colors.neutral[500],\n hexColor: colors.neutral[500],\n backgroundColor: isDark ? colors.neutral[700] : colors.white,\n hexBackgroundColor: isDark ? colors.neutral[700] : colors.white,\n icon: undefined,\n };\n}\n\n/**\n * Get icon and color for a resource type\n *\n * @param resourceType - The resource type (model, source, seed, etc.)\n * @returns Object containing color and icon component\n *\n * @example\n * ```tsx\n * const { color, icon: Icon } = getIconForResourceType(\"model\");\n * return Icon ? <Icon style={{ color }} /> : null;\n * ```\n */\nexport function getIconForResourceType(\n resourceType?: string,\n): ResourceTypeStyle {\n switch (resourceType) {\n case \"model\":\n return {\n color: colors.cyan[200],\n icon: IconModel,\n };\n case \"source\":\n return {\n color: colors.green[300],\n icon: IconSource,\n };\n case \"seed\":\n return {\n color: colors.green[500],\n icon: IconSeed,\n };\n case \"snapshot\":\n return {\n color: colors.green[500],\n icon: IconSnapshot,\n };\n case \"metric\":\n return {\n color: colors.rose[200],\n icon: IconMetric,\n };\n case \"exposure\":\n return {\n color: colors.rose[200],\n icon: IconExposure,\n };\n case \"semantic_model\":\n return {\n color: colors.rose[400],\n icon: IconSemanticModel,\n };\n default:\n return {\n color: \"inherit\",\n icon: undefined,\n };\n }\n}\n\n// =============================================================================\n// STYLE CONSTANTS\n// =============================================================================\n\n/**\n * Pre-defined colors for change status (for direct usage without function call)\n */\nexport const changeStatusColors: Record<ChangeStatus | \"unchanged\", string> = {\n added: colors.green[500],\n removed: colors.red[500],\n modified: colors.amber[500],\n unchanged: colors.neutral[500],\n};\n\n/**\n * Pre-defined background colors for change status (light mode)\n */\nexport const changeStatusBackgroundsLight: Record<\n ChangeStatus | \"unchanged\",\n string\n> = {\n added: colors.green[100],\n removed: colors.red[200],\n modified: colors.amber[100],\n unchanged: colors.white,\n};\n\n/**\n * Pre-defined background colors for change status (dark mode)\n */\nexport const changeStatusBackgroundsDark: Record<\n ChangeStatus | \"unchanged\",\n string\n> = {\n added: colors.green[900],\n removed: colors.red[950],\n modified: colors.amber[900],\n unchanged: colors.neutral[700],\n};\n","\"use client\";\n\nimport {\n BaseEdge,\n type Edge,\n EdgeLabelRenderer,\n type EdgeProps,\n getBezierPath,\n} from \"@xyflow/react\";\nimport { memo } from \"react\";\n\nexport type EdgeChangeStatus = \"added\" | \"removed\" | \"modified\" | \"unchanged\";\n\nexport interface LineageEdgeData extends Record<string, unknown> {\n /** Change status for diff visualization */\n changeStatus?: EdgeChangeStatus;\n /** Whether this edge is highlighted */\n isHighlighted?: boolean;\n /** Label to display on edge */\n label?: string;\n}\n\nexport type LineageEdgeType = Edge<LineageEdgeData>;\n\nexport type LineageEdgeProps = EdgeProps<LineageEdgeType>;\n\nconst statusColors: Record<EdgeChangeStatus, string> = {\n added: \"#22c55e\",\n removed: \"#ef4444\",\n modified: \"#f59e0b\",\n unchanged: \"#94a3b8\",\n};\n\nfunction LineageEdgeComponent({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n data,\n selected,\n}: LineageEdgeProps) {\n const changeStatus: EdgeChangeStatus = data?.changeStatus ?? \"unchanged\";\n const isHighlighted = data?.isHighlighted ?? false;\n const label = data?.label;\n\n const [edgePath, labelX, labelY] = getBezierPath({\n sourceX,\n sourceY,\n sourcePosition,\n targetX,\n targetY,\n targetPosition,\n });\n\n const strokeColor = statusColors[changeStatus];\n const strokeWidth = isHighlighted || selected ? 2.5 : 1.5;\n const strokeOpacity = isHighlighted || selected ? 1 : 0.6;\n\n return (\n <>\n <BaseEdge\n id={id}\n path={edgePath}\n style={{\n stroke: strokeColor,\n strokeWidth,\n opacity: strokeOpacity,\n }}\n />\n {label && (\n <EdgeLabelRenderer>\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,\n fontSize: 10,\n fontWeight: 500,\n background: \"white\",\n padding: \"2px 4px\",\n borderRadius: 4,\n pointerEvents: \"all\",\n }}\n >\n {label}\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n}\n\nexport const LineageEdge = memo(LineageEdgeComponent);\nLineageEdge.displayName = \"LineageEdge\";\n","\"use client\";\n\n/**\n * @file ChangedOnlyCheckbox.tsx\n * @description Checkbox component for filtering to show only changed rows\n *\n * Framework-agnostic component that works with both Recce OSS and Cloud.\n */\n\nimport Checkbox from \"@mui/material/Checkbox\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ChangedOnlyCheckboxProps {\n /** Whether the changed-only filter is enabled */\n changedOnly?: boolean;\n /** Callback when the checkbox state changes */\n onChange: () => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * A checkbox component for filtering diff results to show only changed rows.\n *\n * @example\n * ```tsx\n * <ChangedOnlyCheckbox\n * changedOnly={viewOptions.changed_only}\n * onChange={() => setChangedOnly(!viewOptions.changed_only)}\n * />\n * ```\n */\nexport function ChangedOnlyCheckbox({\n changedOnly,\n onChange,\n}: ChangedOnlyCheckboxProps) {\n return (\n <FormControlLabel\n control={\n <Checkbox\n checked={changedOnly ?? false}\n onChange={() => {\n onChange();\n }}\n size=\"small\"\n />\n }\n label=\"Changed only\"\n slotProps={{\n typography: { variant: \"body2\" },\n }}\n />\n );\n}\n","\"use client\";\n\n/**\n * @file ToggleSwitch.tsx\n * @description Toggle switch component for switching between two values\n *\n * Framework-agnostic toggle switch that works with both Recce OSS and Cloud.\n */\n\nimport Button from \"@mui/material/Button\";\nimport ButtonGroup from \"@mui/material/ButtonGroup\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ToggleSwitchProps {\n /** Current toggle state */\n value: boolean;\n /** Callback when toggle state changes */\n onChange: (value: boolean) => void;\n /** Label for the \"on\" state */\n textOn?: string;\n /** Label for the \"off\" state */\n textOff?: string;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * A toggle switch component that allows switching between two values.\n *\n * @example\n * ```tsx\n * <ToggleSwitch\n * value={isEnabled}\n * onChange={setIsEnabled}\n * textOff=\"Inline\"\n * textOn=\"Side by side\"\n * />\n * ```\n */\nexport function ToggleSwitch({\n value,\n onChange,\n textOn,\n textOff,\n}: ToggleSwitchProps) {\n return (\n <ButtonGroup variant=\"outlined\" size=\"xsmall\" sx={{ borderRadius: 1 }}>\n <Button\n onClick={() => {\n onChange(false);\n }}\n sx={{\n color: !value ? \"text.primary\" : \"text.disabled\",\n bgcolor: !value ? \"background.paper\" : \"action.hover\",\n borderColor: \"divider\",\n \"&:hover\": {\n bgcolor: !value ? \"background.paper\" : \"action.selected\",\n borderColor: \"divider\",\n },\n }}\n >\n {textOff ?? \"Off\"}\n </Button>\n <Button\n onClick={() => {\n onChange(true);\n }}\n sx={{\n color: value ? \"text.primary\" : \"text.disabled\",\n bgcolor: value ? \"background.paper\" : \"action.hover\",\n borderColor: \"divider\",\n \"&:hover\": {\n bgcolor: value ? \"background.paper\" : \"action.selected\",\n borderColor: \"divider\",\n },\n }}\n >\n {textOn ?? \"On\"}\n </Button>\n </ButtonGroup>\n );\n}\n","\"use client\";\n\n/**\n * @file DiffDisplayModeSwitch.tsx\n * @description Switch component for toggling between inline and side-by-side diff display modes\n *\n * Framework-agnostic component that works with both Recce OSS and Cloud.\n */\n\nimport { DiffText } from \"./DiffText\";\nimport { ToggleSwitch } from \"./ToggleSwitch\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DiffDisplayMode = \"inline\" | \"side_by_side\";\n\nexport interface DiffDisplayModeSwitchProps {\n /** Current display mode */\n displayMode: DiffDisplayMode;\n /** Callback when display mode changes */\n onDisplayModeChanged: (displayMode: DiffDisplayMode) => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * A switch component for toggling between inline and side-by-side diff display modes.\n *\n * When in inline mode, also shows color legend (Base = red, Current = green).\n *\n * @example\n * ```tsx\n * <DiffDisplayModeSwitch\n * displayMode=\"inline\"\n * onDisplayModeChanged={(mode) => setDisplayMode(mode)}\n * />\n * ```\n */\nexport function DiffDisplayModeSwitch({\n displayMode,\n onDisplayModeChanged,\n}: DiffDisplayModeSwitchProps) {\n return (\n <>\n {displayMode === \"inline\" && (\n <>\n <DiffText\n value=\"Base\"\n colorPalette=\"red\"\n grayOut={false}\n fontSize=\"10pt\"\n noCopy\n />\n <DiffText\n value=\"Current\"\n colorPalette=\"green\"\n grayOut={false}\n fontSize=\"10pt\"\n noCopy\n />\n </>\n )}\n <ToggleSwitch\n value={displayMode === \"side_by_side\"}\n onChange={(value) => {\n onDisplayModeChanged(value ? \"side_by_side\" : \"inline\");\n }}\n textOff=\"Inline\"\n textOn=\"Side by side\"\n />\n </>\n );\n}\n","/**\n * @file DropdownValuesInput.tsx\n * @description Multi-select dropdown input component with filtering and custom value support\n *\n * Features:\n * - Dropdown menu with suggestion list\n * - Filter/search functionality (case-insensitive)\n * - Chip-based value display with removal\n * - Keyboard navigation (Enter, comma, Backspace)\n * - Custom value addition\n * - Configurable size variants\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Chip from \"@mui/material/Chip\";\nimport Divider from \"@mui/material/Divider\";\nimport InputBase from \"@mui/material/InputBase\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport MuiTooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport _ from \"lodash\";\nimport { type MouseEvent, useRef, useState } from \"react\";\n\n/**\n * Size variants for the dropdown input\n */\nexport type DropdownValuesInputSize = \"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\";\n\n/**\n * Props for the DropdownValuesInput component\n */\nexport interface DropdownValuesInputProps {\n /** Unit name for pluralization in \"X {unitName}s selected\" display */\n unitName: string;\n /** List of suggested values to show in dropdown */\n suggestionList?: string[];\n /** Initial selected values */\n defaultValues?: string[];\n /** Callback when selected values change */\n onValuesChange: (values: string[]) => void;\n /** Optional CSS class name */\n className?: string;\n /** Size variant for the input */\n size?: DropdownValuesInputSize;\n /** Width of the input (CSS value or number in pixels) */\n width?: string | number;\n /** Placeholder text when no values are selected */\n placeholder?: string;\n /** Whether the input is disabled */\n disabled?: boolean;\n}\n\n/**\n * A multi-select dropdown input component for selecting multiple values.\n *\n * Provides a dropdown menu with suggestion list, filtering, and the ability\n * to add custom values. Selected values are displayed as chips that can be\n * removed individually.\n *\n * @example\n * ```tsx\n * <DropdownValuesInput\n * unitName=\"key\"\n * suggestionList={[\"id\", \"name\", \"email\"]}\n * defaultValues={[\"id\"]}\n * onValuesChange={(values) => console.log(values)}\n * placeholder=\"Select or type to add keys\"\n * size=\"sm\"\n * width={240}\n * />\n * ```\n */\nexport const DropdownValuesInput = (props: DropdownValuesInputProps) => {\n const { defaultValues, suggestionList, onValuesChange, className } = props;\n const [values, setValues] = useState<string[]>(defaultValues ?? []);\n const [filter, setFilter] = useState<string>(\"\");\n const [isTyping, setIsTyping] = useState<boolean>(false);\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const open = Boolean(anchorEl);\n\n const showNumberOfValuesSelected = (tags: string[]) => {\n if (tags.length > 1) {\n return `${tags.length} ${props.unitName}s selected`;\n } else if (tags.length === 1) {\n return tags[0];\n }\n return \"\";\n };\n\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(event.currentTarget);\n // Focus input after menu opens\n setTimeout(() => inputRef.current?.focus(), 100);\n };\n\n const handleClose = () => {\n setAnchorEl(null);\n setIsTyping(false);\n };\n\n const handleSelect = (value: string) => {\n if (!values.includes(value)) {\n setFilter(\"\");\n setValues([...values, value]);\n onValuesChange([...values, value]);\n }\n };\n\n const handleClear = () => {\n setFilter(\"\");\n setValues([]);\n onValuesChange([]);\n };\n\n const handleRemoveValue = (value: string) => {\n setValues(values.filter((v) => v !== value));\n onValuesChange(values.filter((v) => v !== value));\n };\n\n // Filter the suggestion list without case sensitivity based on the current input\n const lowerCaseFilter = filter.toLowerCase();\n const filteredList =\n suggestionList\n ?.filter(\n (value) =>\n lowerCaseFilter === \"\" ||\n value.toLowerCase().includes(lowerCaseFilter),\n )\n .filter((value) => !values.includes(value)) ?? [];\n const limit = 10;\n\n // Size mapping for font sizes\n const getFontSize = () => {\n switch (props.size) {\n case \"2xs\":\n return \"0.625rem\";\n case \"xs\":\n return \"0.75rem\";\n case \"sm\":\n return \"0.875rem\";\n default:\n return \"0.875rem\";\n }\n };\n\n const getHeight = () => {\n switch (props.size) {\n case \"2xs\":\n return 24;\n case \"xs\":\n return 28;\n case \"sm\":\n return 32;\n default:\n return 36;\n }\n };\n\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n width: props.width,\n }}\n className={className}\n >\n <Button\n variant=\"outlined\"\n color=\"neutral\"\n size=\"small\"\n onClick={handleClick}\n disabled={props.disabled}\n sx={{\n width: \"100%\",\n height: getHeight(),\n justifyContent: \"space-between\",\n textTransform: \"none\",\n fontSize: getFontSize(),\n fontWeight: \"normal\",\n px: 1,\n }}\n >\n <Typography\n component=\"span\"\n sx={{\n fontSize: getFontSize(),\n color: values.length > 0 ? \"text.primary\" : \"text.secondary\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {showNumberOfValuesSelected(values) || props.placeholder || \"\"}\n </Typography>\n {values.length > 0 && (\n <Typography\n component=\"span\"\n onClick={(e) => {\n e.stopPropagation();\n handleClear();\n }}\n sx={{\n fontSize: getFontSize(),\n color: \"primary.main\",\n cursor: \"pointer\",\n ml: 1,\n \"&:hover\": { textDecoration: \"underline\" },\n }}\n >\n Clear\n </Typography>\n )}\n </Button>\n\n <Menu\n anchorEl={anchorEl}\n open={open}\n onClose={handleClose}\n slotProps={{\n paper: {\n sx: {\n width: props.width,\n fontSize: getFontSize(),\n },\n },\n }}\n >\n {/* Input Filter & Show Tags */}\n <Box sx={{ px: 0.5, py: 0.5 }}>\n <Box\n sx={{\n border: \"1px solid\",\n borderColor: \"divider\",\n borderRadius: 1,\n p: 0.5,\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: 0.5,\n alignItems: \"center\",\n }}\n className=\"no-track-pii-safe\"\n >\n {values.map((value) => (\n <Chip\n key={value}\n label={value}\n size=\"small\"\n onDelete={() => handleRemoveValue(value)}\n sx={{ height: 22, fontSize: getFontSize() }}\n />\n ))}\n <InputBase\n inputRef={inputRef}\n placeholder=\"Filter or add custom keys\"\n value={filter}\n onChange={(e) => {\n setFilter(e.target.value);\n setIsTyping(true);\n }}\n onKeyDown={(e) => {\n // Stop propagation to prevent MUI Menu's typeahead navigation\n // from intercepting key presses (except Escape to close menu)\n if (e.key !== \"Escape\") {\n e.stopPropagation();\n }\n\n const target = e.target as HTMLInputElement;\n const newText = target.value.trim().replace(\",\", \"\");\n switch (e.key) {\n case \",\":\n case \"Enter\":\n e.preventDefault();\n if (newText) {\n handleSelect(newText);\n setFilter(\"\");\n }\n break;\n case \"Backspace\":\n if (target.value === \"\" && values.length > 0) {\n setValues(values.slice(0, -1));\n onValuesChange(values.slice(0, -1));\n }\n break;\n default:\n break;\n }\n }}\n onBlur={() => {\n if (inputRef.current && isTyping) {\n inputRef.current.focus();\n }\n }}\n sx={{\n flex: 1,\n minWidth: 120,\n fontSize: getFontSize(),\n \"& input\": {\n p: 0.5,\n },\n }}\n />\n </Box>\n </Box>\n\n <Divider />\n\n {/* Suggestion List */}\n {filter !== \"\" && !suggestionList?.includes(filter) && (\n <MenuItem\n onClick={() => {\n handleSelect(filter);\n setIsTyping(false);\n }}\n sx={{ fontSize: getFontSize() }}\n >\n Add '{filter}' to the list\n </MenuItem>\n )}\n {filteredList.slice(0, limit).map((value, cid) => (\n <MenuItem\n key={_.uniqueId(`option-${cid}`)}\n onClick={() => {\n handleSelect(value);\n }}\n sx={{ fontSize: getFontSize() }}\n >\n {value}\n </MenuItem>\n ))}\n {filteredList.length > limit && (\n <MuiTooltip\n title=\"Please use filter to find more items\"\n placement=\"top\"\n >\n <Box px={1.5} py={0.5} color=\"text.secondary\" fontSize=\"8pt\">\n and {filteredList.length - limit} more items...\n </Box>\n </MuiTooltip>\n )}\n </Menu>\n </Box>\n );\n};\n","\"use client\";\n\n/**\n * @file HistogramDiffForm.tsx\n * @description Form component for configuring histogram diff parameters\n *\n * This component allows users to select a column for histogram diff comparison.\n * It filters columns to only show numeric columns (excluding string, boolean, and datetime types).\n */\n\nimport Box from \"@mui/material/Box\";\nimport FormControl from \"@mui/material/FormControl\";\nimport FormLabel from \"@mui/material/FormLabel\";\nimport NativeSelect from \"@mui/material/NativeSelect\";\nimport type { HistogramDiffParams } from \"../../api\";\nimport { useModelColumns } from \"../../hooks\";\nimport type { RunFormProps } from \"../run\";\n\n// ============================================================================\n// Type Utilities\n// ============================================================================\n\nfunction isStringDataType(columnType: string) {\n const stringDataTypes = [\n \"CHAR\",\n \"VARCHAR\",\n \"TINYTEXT\",\n \"TEXT\",\n \"MEDIUMTEXT\",\n \"LONGTEXT\",\n \"NCHAR\",\n \"NVARCHAR\",\n \"VARCHAR2\",\n \"NVARCHAR2\",\n \"CLOB\",\n \"NCLOB\",\n \"VARCHAR(MAX)\",\n \"XML\",\n \"JSON\",\n ];\n // Normalize columnType by removing spaces and converting to uppercase\n const normalizedType = columnType.trim().toUpperCase();\n\n // Check if columnType is in the predefined list\n if (stringDataTypes.includes(normalizedType)) {\n return true;\n }\n\n // Match types that have a length specification (e.g., VARCHAR(255))\n const regex = /^(VARCHAR|NVARCHAR|VARCHAR2|NVARCHAR2|CHAR|NCHAR)\\(\\d+\\)$/;\n return regex.test(normalizedType);\n}\n\nfunction isBooleanDataType(columnType: string) {\n const booleanDataTypes = [\n \"BOOLEAN\", // PostgreSQL, SQLite, and others with native boolean support\n \"TINYINT(1)\", // MySQL/MariaDB uses TINYINT(1) to represent boolean values\n \"BIT\", // SQL Server and others use BIT to represent boolean values, where 1 is true and 0 is false\n \"NUMBER(1)\", // Oracle uses NUMBER(1) where 1 is true and 0 is false, as it does not have a native BOOLEAN type\n \"BOOL\", // Snowflake and PostgreSQL also support BOOL as an alias for BOOLEAN\n ];\n return booleanDataTypes.includes(columnType.toUpperCase());\n}\n\nfunction isDateTimeType(columnType: string) {\n const sql_datetime_types = [\n \"DATE\",\n \"DATETIME\",\n \"TIMESTAMP\",\n \"TIME\",\n \"YEAR\", // Specific to MySQL/MariaDB\n \"DATETIME2\",\n \"SMALLDATETIME\",\n \"DATETIMEOFFSET\", // Specific to SQL Server\n \"INTERVAL\", // Common in PostgreSQL and Oracle\n \"TIMESTAMPTZ\",\n \"TIMETZ\", // Specific to PostgreSQL\n \"TIMESTAMP WITH TIME ZONE\",\n \"TIMESTAMP WITH LOCAL TIME ZONE\", // Oracle\n \"TIMESTAMP_LTZ\",\n \"TIMESTAMP_NTZ\",\n \"TIMESTAMP_TZ\", // Specific to Snowflake\n ];\n return sql_datetime_types.includes(columnType.toUpperCase());\n}\n\n// ============================================================================\n// Public Utilities\n// ============================================================================\n\n/**\n * Check if a column type supports histogram diff\n * Returns true for numeric types (excludes string, boolean, and datetime)\n */\nexport function supportsHistogramDiff(columnType: string) {\n return (\n !isStringDataType(columnType) &&\n !isBooleanDataType(columnType) &&\n !isDateTimeType(columnType)\n );\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\ntype HistogramDiffEditProps = RunFormProps<HistogramDiffParams>;\n\n/**\n * Form component for configuring histogram diff parameters\n *\n * Displays a dropdown to select a column for histogram diff comparison.\n * Only numeric columns are shown (string, boolean, and datetime types are filtered out).\n *\n * @example\n * ```tsx\n * <HistogramDiffForm\n * params={{ model: \"orders\", column_name: \"\" }}\n * onParamsChanged={(params) => setParams(params)}\n * setIsReadyToExecute={(ready) => setReady(ready)}\n * />\n * ```\n */\nexport function HistogramDiffForm({\n params,\n onParamsChanged,\n setIsReadyToExecute,\n}: HistogramDiffEditProps) {\n const {\n columns: allColumns,\n isLoading,\n error,\n } = useModelColumns(params.model);\n const columns = allColumns.filter(\n (c) =>\n !isStringDataType(c.type) &&\n !isBooleanDataType(c.type) &&\n !isDateTimeType(c.type),\n );\n\n if (isLoading) {\n return <Box>Loading...</Box>;\n }\n\n if (allColumns.length === 0 || error) {\n return (\n <Box>\n Error: Please provide the 'catalog.json' to list column\n candidates\n </Box>\n );\n }\n\n return (\n <Box sx={{ m: \"16px\" }}>\n <FormControl fullWidth disabled={columns.length === 0}>\n <FormLabel sx={{ mb: 1 }}>\n Pick a column to show Histogram Diff\n </FormLabel>\n <NativeSelect\n value={params.column_name}\n onChange={(e) => {\n const columnName = e.target.value;\n setIsReadyToExecute(!!columnName);\n const columnType =\n columns.find((c) => c.name === columnName)?.type ?? \"\";\n onParamsChanged({\n ...params,\n column_name: columnName,\n column_type: columnType,\n });\n }}\n >\n <option value=\"\">\n {columns.length !== 0\n ? \"Select column\"\n : \"No numeric column is available\"}\n </option>\n {columns.map((c) => (\n <option key={c.name} value={c.name} className=\"no-track-pii-safe\">\n {c.name} : {c.type}\n </option>\n ))}\n </NativeSelect>\n </FormControl>\n </Box>\n );\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Chip from \"@mui/material/Chip\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\n\n/**\n * Legend item for change status\n */\nexport interface ChangeStatusLegendItem {\n status: \"added\" | \"removed\" | \"modified\";\n label: string;\n description?: string;\n}\n\n/**\n * Legend item for transformation type\n */\nexport interface TransformationLegendItem {\n type: \"passthrough\" | \"renamed\" | \"derived\" | \"source\" | \"unknown\";\n label: string;\n description?: string;\n}\n\n/**\n * Props for the LineageLegend component\n */\nexport interface LineageLegendProps {\n /**\n * Type of legend to display\n */\n variant: \"changeStatus\" | \"transformation\";\n\n /**\n * Whether to show tooltips on hover\n * @default true\n */\n showTooltips?: boolean;\n\n /**\n * Optional title for the legend\n */\n title?: string;\n\n /**\n * CSS class name for additional styling\n */\n className?: string;\n}\n\n/**\n * Default change status items\n */\nconst defaultChangeStatusItems: ChangeStatusLegendItem[] = [\n { status: \"added\", label: \"Added\", description: \"Newly added resource\" },\n { status: \"removed\", label: \"Removed\", description: \"Removed resource\" },\n { status: \"modified\", label: \"Modified\", description: \"Modified resource\" },\n];\n\n/**\n * Default transformation items\n */\nconst defaultTransformationItems: TransformationLegendItem[] = [\n {\n type: \"passthrough\",\n label: \"Passthrough\",\n description: \"Column passes through unchanged\",\n },\n {\n type: \"renamed\",\n label: \"Renamed\",\n description: \"Column was renamed from source\",\n },\n {\n type: \"derived\",\n label: \"Derived\",\n description: \"Column is derived from other columns\",\n },\n { type: \"source\", label: \"Source\", description: \"Original source column\" },\n {\n type: \"unknown\",\n label: \"Unknown\",\n description: \"Transformation type could not be determined\",\n },\n];\n\n/**\n * Colors for change status indicators\n */\nconst changeStatusStyles: Record<string, { color: string; symbol: string }> = {\n added: { color: \"#22c55e\", symbol: \"+\" },\n removed: { color: \"#ef4444\", symbol: \"-\" },\n modified: { color: \"#f59e0b\", symbol: \"~\" },\n};\n\n/**\n * Colors for transformation type chips\n */\nconst transformationStyles: Record<\n string,\n { letter: string; color: \"default\" | \"warning\" | \"info\" | \"error\" }\n> = {\n passthrough: { letter: \"P\", color: \"default\" },\n renamed: { letter: \"R\", color: \"warning\" },\n derived: { letter: \"D\", color: \"warning\" },\n source: { letter: \"S\", color: \"info\" },\n unknown: { letter: \"U\", color: \"error\" },\n};\n\n/**\n * ChangeStatusIcon - Renders a change status indicator\n */\nfunction ChangeStatusIcon({\n status,\n}: {\n status: \"added\" | \"removed\" | \"modified\";\n}) {\n const style = changeStatusStyles[status];\n return (\n <Box\n sx={{\n width: 16,\n height: 16,\n borderRadius: \"50%\",\n backgroundColor: style.color,\n color: \"white\",\n fontSize: 10,\n fontWeight: \"bold\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flexShrink: 0,\n }}\n >\n {style.symbol}\n </Box>\n );\n}\n\n/**\n * TransformationChip - Renders a transformation type chip\n */\nfunction TransformationChip({\n type,\n}: {\n type: \"passthrough\" | \"renamed\" | \"derived\" | \"source\" | \"unknown\";\n}) {\n const style = transformationStyles[type];\n return (\n <Chip\n label={style.letter}\n size=\"small\"\n color={style.color}\n sx={{\n fontSize: \"8pt\",\n height: 18,\n minWidth: 18,\n \"& .MuiChip-label\": {\n px: 0.5,\n },\n }}\n />\n );\n}\n\n/**\n * LineageLegend Component\n *\n * A pure presentation component for displaying legends in lineage visualizations.\n * Supports both change status legends (added/removed/modified) and\n * transformation type legends (passthrough/renamed/derived/source/unknown).\n *\n * @example Change status legend\n * ```tsx\n * import { LineageLegend } from '@datarecce/ui/primitives';\n *\n * function MyLineageGraph() {\n * return (\n * <div style={{ position: 'relative' }}>\n * <ReactFlow nodes={nodes} edges={edges} />\n * <div style={{ position: 'absolute', bottom: 10, right: 10 }}>\n * <LineageLegend variant=\"changeStatus\" title=\"Changes\" />\n * </div>\n * </div>\n * );\n * }\n * ```\n *\n * @example Transformation type legend\n * ```tsx\n * import { LineageLegend } from '@datarecce/ui/primitives';\n *\n * function ColumnLineageGraph() {\n * return (\n * <div>\n * <ReactFlow nodes={columnNodes} edges={edges} />\n * <LineageLegend variant=\"transformation\" />\n * </div>\n * );\n * }\n * ```\n */\nexport function LineageLegend({\n variant,\n showTooltips = true,\n title,\n className,\n}: LineageLegendProps) {\n const items =\n variant === \"changeStatus\"\n ? defaultChangeStatusItems\n : defaultTransformationItems;\n\n return (\n <Box\n className={className}\n sx={{\n bgcolor: \"background.paper\",\n padding: \"12px\",\n border: \"1px solid\",\n borderColor: \"divider\",\n borderRadius: 1,\n fontSize: \"0.875rem\",\n }}\n >\n {title && (\n <Typography\n variant=\"caption\"\n sx={{\n display: \"block\",\n fontWeight: 600,\n mb: 1,\n color: \"text.secondary\",\n }}\n >\n {title}\n </Typography>\n )}\n\n {variant === \"changeStatus\" &&\n (items as ChangeStatusLegendItem[]).map((item) => {\n const content = (\n <Box\n key={item.status}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n mb: \"4px\",\n \"&:last-child\": { mb: 0 },\n }}\n >\n <ChangeStatusIcon status={item.status} />\n <Typography variant=\"body2\">{item.label}</Typography>\n </Box>\n );\n\n return showTooltips && item.description ? (\n <Tooltip\n key={item.status}\n title={item.description}\n placement=\"right\"\n >\n {content}\n </Tooltip>\n ) : (\n content\n );\n })}\n\n {variant === \"transformation\" &&\n (items as TransformationLegendItem[]).map((item) => {\n const content = (\n <Box\n key={item.type}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n mb: \"4px\",\n \"&:last-child\": { mb: 0 },\n }}\n >\n <TransformationChip type={item.type} />\n <Typography variant=\"body2\">{item.label}</Typography>\n </Box>\n );\n\n return showTooltips && item.description ? (\n <Tooltip key={item.type} title={item.description} placement=\"right\">\n {content}\n </Tooltip>\n ) : (\n content\n );\n })}\n </Box>\n );\n}\n","\"use client\";\n\n/**\n * @file LineageNode.tsx\n * @description Pure presentation component for displaying nodes in a lineage graph\n *\n * This component is designed to work with @xyflow/react and receives all data via props.\n * It does not perform any data fetching or state management - it is purely presentational.\n *\n * Features:\n * - Change status visualization (added, removed, modified, unchanged)\n * - Selection modes (normal, selecting, action_result)\n * - Interactive checkbox for multi-select\n * - Action tag display for run status\n * - Resource type and change status icons\n * - Hover menu with context actions\n * - Column container support\n *\n * Source: Enhanced from UI package primitive + OSS js/src/components/lineage/GraphNode.tsx\n */\n\nimport Box from \"@mui/material/Box\";\nimport Checkbox from \"@mui/material/Checkbox\";\nimport Stack from \"@mui/material/Stack\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport { Handle, Position } from \"@xyflow/react\";\nimport { type MouseEvent, memo, type ReactNode, useState } from \"react\";\nimport { getIconForChangeStatus, getIconForResourceType } from \"../styles\";\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\n/**\n * Change status for node visualization\n */\nexport type NodeChangeStatus = \"added\" | \"removed\" | \"modified\" | \"unchanged\";\n\n/**\n * Selection mode for the node\n */\nexport type SelectMode = \"normal\" | \"selecting\" | \"action_result\";\n\n/**\n * Change category from column-level lineage analysis\n */\nexport type ChangeCategory =\n | \"breaking\"\n | \"non_breaking\"\n | \"partial_breaking\"\n | \"unknown\";\n\n/**\n * Data structure for lineage node\n */\nexport interface LineageNodeData extends Record<string, unknown> {\n /** Display label for the node */\n label: string;\n /** Node type (model, source, seed, etc.) */\n nodeType?: string;\n /** Change status for diff visualization */\n changeStatus?: NodeChangeStatus;\n /** Whether this node is currently selected */\n isSelected?: boolean;\n /** Resource type for icon display */\n resourceType?: string;\n /** Package name */\n packageName?: string;\n /** Whether to show column-level details */\n showColumns?: boolean;\n /** Column data if showing columns */\n columns?: Array<{\n name: string;\n type?: string;\n changeStatus?: NodeChangeStatus;\n }>;\n}\n\n/**\n * Props for LineageNode component\n */\nexport interface LineageNodeProps {\n /** Node ID */\n id: string;\n /** Node data */\n data: LineageNodeData;\n /** Whether the node is selected (from React Flow) */\n selected?: boolean;\n\n // === Interactive Mode Props ===\n /** Enable interactive mode with checkbox */\n interactive?: boolean;\n /** Selection mode */\n selectMode?: SelectMode;\n /** Whether the node is selected (checkbox state) */\n isNodeSelected?: boolean;\n /** Whether the node is focused */\n isFocused?: boolean;\n /** Whether the node is highlighted */\n isHighlighted?: boolean;\n /** Whether to show content (zoom level visibility) */\n showContent?: boolean;\n\n // === Action Display Props ===\n /** Action tag to display (for action_result mode) */\n actionTag?: ReactNode;\n /** Whether to show change analysis mode */\n showChangeAnalysis?: boolean;\n /** Change category text */\n changeCategory?: ChangeCategory;\n /** Runs aggregated display component */\n runsAggregatedTag?: ReactNode;\n\n // === Layout Props ===\n /** Whether node has parent nodes (show left handle) */\n hasParents?: boolean;\n /** Whether node has child nodes (show right handle) */\n hasChildren?: boolean;\n /** Number of columns for column container height */\n columnCount?: number;\n /** Height per column in pixels */\n columnHeight?: number;\n\n // === Theme Props ===\n /** Whether dark mode is active */\n isDark?: boolean;\n\n // === Callbacks ===\n /** Callback when node is clicked */\n onNodeClick?: (nodeId: string) => void;\n /** Callback when node is double-clicked */\n onNodeDoubleClick?: (nodeId: string) => void;\n /** Callback when checkbox is clicked */\n onSelect?: (nodeId: string) => void;\n /** Callback when context menu is requested */\n onContextMenu?: (event: MouseEvent, nodeId: string) => void;\n /** Callback when impact radius button is clicked */\n onShowImpactRadius?: (nodeId: string) => void;\n}\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst CHANGE_CATEGORY_LABELS: Record<ChangeCategory, string> = {\n breaking: \"Breaking\",\n non_breaking: \"Non Breaking\",\n partial_breaking: \"Partial Breaking\",\n unknown: \"Unknown\",\n};\n\nconst DEFAULT_COLUMN_HEIGHT = 28;\n\n// =============================================================================\n// ICONS\n// =============================================================================\n\n/**\n * Kebab menu icon\n */\nconst KebabIcon = () => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M8 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm0 6.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm0 6.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z\" />\n </svg>\n);\n\n/**\n * Impact radius icon (dot circle)\n */\nconst ImpactRadiusIcon = () => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M256 56c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m0-48C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 168c-44.183 0-80 35.817-80 80s35.817 80 80 80 80-35.817 80-80-35.817-80-80-80z\" />\n </svg>\n);\n\n// =============================================================================\n// HELPER COMPONENTS\n// =============================================================================\n\n/**\n * Node title with tooltip\n */\nfunction NodeTitle({\n name,\n color,\n resourceType,\n}: {\n name: string;\n color: string;\n resourceType?: string;\n}) {\n const tooltipTitle =\n resourceType === \"model\" ? name : `${name} (${resourceType || \"unknown\"})`;\n\n return (\n <Box\n sx={{\n flex: 1,\n color,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n <Tooltip title={tooltipTitle} placement=\"top\">\n <span>{name}</span>\n </Tooltip>\n </Box>\n );\n}\n\n// =============================================================================\n// MAIN COMPONENT\n// =============================================================================\n\n/**\n * LineageNode - Pure presentation component for displaying nodes in a lineage graph\n *\n * This component is designed to be used with @xyflow/react and receives all data via props.\n * It does not perform any data fetching or state management - it is purely presentational.\n *\n * @example Basic usage\n * ```tsx\n * <LineageNode\n * id=\"model.my_model\"\n * data={{\n * label: \"my_model\",\n * nodeType: \"model\",\n * changeStatus: \"modified\",\n * resourceType: \"model\"\n * }}\n * selected={false}\n * onNodeClick={(nodeId) => console.log(\"Clicked:\", nodeId)}\n * />\n * ```\n *\n * @example Interactive mode with selection\n * ```tsx\n * <LineageNode\n * id=\"model.my_model\"\n * data={{ label: \"my_model\", changeStatus: \"added\" }}\n * interactive\n * selectMode=\"selecting\"\n * isNodeSelected={selectedNodes.has(\"model.my_model\")}\n * onSelect={(nodeId) => toggleSelection(nodeId)}\n * />\n * ```\n *\n * @example Action result mode\n * ```tsx\n * <LineageNode\n * id=\"model.my_model\"\n * data={{ label: \"my_model\", changeStatus: \"modified\" }}\n * selectMode=\"action_result\"\n * actionTag={<ActionTag status=\"running\" progress={{ percentage: 0.5 }} />}\n * />\n * ```\n */\nfunction LineageNodeComponent({\n id,\n data,\n selected,\n // Interactive props\n interactive = false,\n selectMode = \"normal\",\n isNodeSelected = false,\n isFocused = false,\n isHighlighted = true,\n showContent = true,\n // Action display props\n actionTag,\n showChangeAnalysis = false,\n changeCategory,\n runsAggregatedTag,\n // Layout props\n hasParents = true,\n hasChildren = true,\n columnCount = 0,\n columnHeight = DEFAULT_COLUMN_HEIGHT,\n // Theme props\n isDark = false,\n // Callbacks\n onNodeClick,\n onNodeDoubleClick,\n onSelect,\n onContextMenu,\n onShowImpactRadius,\n}: LineageNodeProps) {\n const [isHovered, setIsHovered] = useState(false);\n\n const {\n label,\n changeStatus = \"unchanged\",\n isSelected: dataIsSelected,\n resourceType,\n } = data;\n\n // Use isNodeSelected prop, fall back to data.isSelected, then to selected\n const isSelected = isNodeSelected || dataIsSelected || selected || false;\n const showColumns = columnCount > 0;\n const hasAction = selectMode === \"action_result\" && actionTag;\n\n // Get icons and colors\n const {\n icon: IconChangeStatus,\n color: colorChangeStatus,\n backgroundColor: backgroundColorChangeStatus,\n } = getIconForChangeStatus(changeStatus, isDark);\n const { icon: ResourceIcon } = getIconForResourceType(resourceType);\n\n // Calculate styles based on state\n const borderWidth = \"2px\";\n const borderColor = colorChangeStatus;\n\n // Node background color logic\n const nodeBackgroundColor = (() => {\n const paperBg = isDark ? \"#1e1e1e\" : \"#ffffff\";\n\n if (showContent) {\n if (selectMode === \"selecting\") {\n return isSelected ? colorChangeStatus : paperBg;\n }\n if (selectMode === \"action_result\") {\n if (!hasAction) return paperBg;\n return isFocused || isSelected || isHovered\n ? backgroundColorChangeStatus\n : colorChangeStatus;\n }\n return isFocused || isSelected || isHovered\n ? backgroundColorChangeStatus\n : paperBg;\n }\n return isFocused || isSelected || isHovered\n ? colorChangeStatus\n : backgroundColorChangeStatus;\n })();\n\n // Text color logic\n const titleColor = (() => {\n const primaryText = isDark ? \"#ffffff\" : \"#000000\";\n const invertedText = isDark ? \"#000000\" : \"#ffffff\";\n\n if (selectMode === \"selecting\") {\n return isSelected ? invertedText : primaryText;\n }\n if (selectMode === \"action_result\") {\n return hasAction && !isSelected ? invertedText : primaryText;\n }\n return primaryText;\n })();\n\n const iconColor = (() => {\n const primaryText = isDark ? \"#ffffff\" : \"#000000\";\n const invertedText = isDark ? \"#000000\" : \"#ffffff\";\n\n if (selectMode === \"selecting\") {\n return isSelected ? invertedText : primaryText;\n }\n if (selectMode === \"action_result\") {\n return hasAction && !isSelected ? invertedText : primaryText;\n }\n return primaryText;\n })();\n\n const changeStatusIconColor = (() => {\n const primaryText = isDark ? \"#ffffff\" : \"#000000\";\n const invertedText = isDark ? \"#000000\" : \"#ffffff\";\n\n if (selectMode === \"selecting\") {\n return isSelected ? invertedText : colorChangeStatus;\n }\n if (selectMode === \"action_result\") {\n return hasAction && !isSelected ? invertedText : primaryText;\n }\n return colorChangeStatus;\n })();\n\n // Filter for dimming\n const nodeFilter = (() => {\n if (selectMode === \"action_result\") {\n return hasAction ? \"none\" : \"opacity(0.2) grayscale(50%)\";\n }\n return isHighlighted || isFocused || isSelected || isHovered\n ? \"none\"\n : \"opacity(0.2) grayscale(50%)\";\n })();\n\n const handleCheckboxClick = (e: MouseEvent) => {\n if (selectMode === \"action_result\") return;\n e.stopPropagation();\n onSelect?.(id);\n };\n\n const handleContextMenuClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onContextMenu?.(e, id);\n };\n\n const handleImpactRadiusClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onShowImpactRadius?.(id);\n };\n\n return (\n <Box\n onClick={() => onNodeClick?.(id)}\n onDoubleClick={() => onNodeDoubleClick?.(id)}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n width: 300,\n cursor: selectMode === \"selecting\" ? \"pointer\" : \"inherit\",\n transition: \"box-shadow 0.2s ease-in-out\",\n padding: 0,\n filter: nodeFilter,\n }}\n >\n {/* Main node container */}\n <Box\n sx={{\n display: \"flex\",\n borderColor,\n borderWidth,\n borderStyle: \"solid\",\n borderTopLeftRadius: 8,\n borderTopRightRadius: 8,\n borderBottomLeftRadius: showColumns ? 0 : 8,\n borderBottomRightRadius: showColumns ? 0 : 8,\n backgroundColor: nodeBackgroundColor,\n height: 60,\n }}\n >\n {/* Left panel with checkbox */}\n <Box\n sx={{\n display: \"flex\",\n bgcolor: colorChangeStatus,\n padding: interactive ? \"8px\" : \"2px\",\n borderRightWidth: borderWidth,\n borderRightStyle: \"solid\",\n borderColor: selectMode === \"selecting\" ? \"#00000020\" : borderColor,\n alignItems: \"top\",\n visibility: showContent ? \"inherit\" : \"hidden\",\n }}\n >\n {interactive && (\n <Checkbox\n checked={\n (selectMode === \"selecting\" && isSelected) ||\n (selectMode === \"action_result\" && !!hasAction)\n }\n onClick={handleCheckboxClick}\n disabled={selectMode === \"action_result\"}\n size=\"small\"\n sx={{\n padding: 0,\n color: \"inherit\",\n \"&.Mui-checked\": { color: \"inherit\" },\n }}\n />\n )}\n </Box>\n\n {/* Content area */}\n <Box\n sx={{\n display: \"flex\",\n flex: \"1 0 auto\",\n mx: 0.5,\n width: 100,\n flexDirection: \"column\",\n }}\n >\n {/* Title row */}\n <Box\n sx={{\n display: \"flex\",\n width: \"100%\",\n textAlign: \"left\",\n fontWeight: 600,\n flex: 1,\n p: 0.5,\n gap: \"5px\",\n alignItems: \"center\",\n visibility: showContent ? \"inherit\" : \"hidden\",\n }}\n >\n <NodeTitle\n name={label}\n color={titleColor}\n resourceType={resourceType}\n />\n\n {/* Hover actions vs icons */}\n {isHovered ? (\n <>\n {changeStatus === \"modified\" && onShowImpactRadius && (\n <Tooltip title=\"Show Impact Radius\" placement=\"top\">\n <Box\n onClick={handleImpactRadiusClick}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"pointer\",\n color: \"text.secondary\",\n \"&:hover\": { color: \"text.primary\" },\n }}\n >\n <ImpactRadiusIcon />\n </Box>\n </Tooltip>\n )}\n {onContextMenu && (\n <Box\n onClick={handleContextMenuClick}\n sx={{\n cursor: \"pointer\",\n color: \"text.secondary\",\n \"&:hover\": { color: \"text.primary\" },\n }}\n >\n <KebabIcon />\n </Box>\n )}\n </>\n ) : (\n <>\n {ResourceIcon && (\n <Box sx={{ fontSize: 16, color: iconColor }}>\n <ResourceIcon />\n </Box>\n )}\n {changeStatus && IconChangeStatus && (\n <Box sx={{ color: changeStatusIconColor }}>\n <IconChangeStatus />\n </Box>\n )}\n </>\n )}\n </Box>\n\n {/* Bottom row - action tags, change analysis, or runs aggregated */}\n <Box\n sx={{\n display: \"flex\",\n flex: \"1 0 auto\",\n mx: 0.5,\n flexDirection: \"column\",\n paddingBottom: 0.5,\n visibility: showContent ? \"inherit\" : \"hidden\",\n }}\n >\n <Stack direction=\"row\" spacing={1}>\n {actionTag ? (\n <>\n <Box sx={{ flexGrow: 1 }} />\n {actionTag}\n </>\n ) : showChangeAnalysis && changeCategory ? (\n <Typography\n sx={{\n height: 20,\n color: \"text.secondary\",\n fontSize: \"9pt\",\n margin: 0,\n fontWeight: 600,\n }}\n >\n {CHANGE_CATEGORY_LABELS[changeCategory]}\n </Typography>\n ) : selectMode !== \"action_result\" && runsAggregatedTag ? (\n runsAggregatedTag\n ) : null}\n </Stack>\n </Box>\n </Box>\n </Box>\n\n {/* Column container */}\n {showColumns && (\n <Box\n sx={{\n p: \"10px 10px\",\n borderColor,\n borderWidth,\n borderStyle: \"solid\",\n borderTopWidth: 0,\n borderBottomLeftRadius: 8,\n borderBottomRightRadius: 8,\n }}\n >\n <Box\n sx={{\n height: `${columnCount * columnHeight}px`,\n overflow: \"auto\",\n }}\n />\n </Box>\n )}\n\n {/* Handles */}\n {hasParents && (\n <Handle type=\"target\" position={Position.Left} isConnectable={false} />\n )}\n {hasChildren && (\n <Handle type=\"source\" position={Position.Right} isConnectable={false} />\n )}\n </Box>\n );\n}\n\nexport const LineageNode = memo(LineageNodeComponent);\nLineageNode.displayName = \"LineageNode\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport ButtonGroup from \"@mui/material/ButtonGroup\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport { type MouseEvent, memo, useState } from \"react\";\n\n/**\n * Action types available for checks\n */\nexport type CheckActionType =\n | \"run\"\n | \"approve\"\n | \"edit\"\n | \"delete\"\n | \"duplicate\"\n | \"copy\"\n | \"preset\";\n\n/**\n * Configuration for a check action\n */\nexport interface CheckAction {\n /** Action type identifier */\n type: CheckActionType;\n /** Display label */\n label: string;\n /** Icon element (optional) */\n icon?: React.ReactNode;\n /** Whether the action is disabled */\n disabled?: boolean;\n /** Tooltip for disabled state */\n disabledTooltip?: string;\n /** Whether this is a destructive action (shown in red) */\n destructive?: boolean;\n}\n\n/**\n * Props for the CheckActions component\n */\nexport interface CheckActionsProps {\n /** ID of the check these actions are for */\n checkId: string;\n /** List of primary actions (shown as buttons) */\n primaryActions?: CheckAction[];\n /** List of secondary actions (shown in dropdown menu) */\n secondaryActions?: CheckAction[];\n /** Callback when an action is triggered */\n onAction?: (checkId: string, actionType: CheckActionType) => void;\n /** Render variant */\n variant?: \"buttons\" | \"menu\" | \"combined\";\n /** Size of buttons */\n size?: \"small\" | \"medium\";\n /** Icon for menu trigger button */\n menuIcon?: React.ReactNode;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * Default menu icon (three vertical dots)\n */\nconst DefaultMenuIcon = () => (\n <Box\n component=\"span\"\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n \"& > span\": {\n width: 4,\n height: 4,\n borderRadius: \"50%\",\n backgroundColor: \"currentColor\",\n },\n }}\n >\n <span />\n <span />\n <span />\n </Box>\n);\n\n/**\n * CheckActions Component\n *\n * A pure presentation component for displaying action buttons/menu for a check.\n * Supports primary actions as buttons and secondary actions in a dropdown menu.\n *\n * @example Basic usage with buttons\n * ```tsx\n * import { CheckActions } from '@datarecce/ui/primitives';\n *\n * <CheckActions\n * checkId={check.id}\n * primaryActions={[\n * { type: 'run', label: 'Run' },\n * { type: 'approve', label: 'Approve' },\n * ]}\n * onAction={(id, action) => handleAction(id, action)}\n * />\n * ```\n *\n * @example Combined buttons and menu\n * ```tsx\n * <CheckActions\n * checkId={check.id}\n * variant=\"combined\"\n * primaryActions={[\n * { type: 'run', label: 'Run', icon: <PlayIcon /> },\n * ]}\n * secondaryActions={[\n * { type: 'duplicate', label: 'Duplicate' },\n * { type: 'copy', label: 'Copy Markdown' },\n * { type: 'delete', label: 'Delete', destructive: true },\n * ]}\n * onAction={(id, action) => handleAction(id, action)}\n * />\n * ```\n *\n * @example Menu only\n * ```tsx\n * <CheckActions\n * checkId={check.id}\n * variant=\"menu\"\n * secondaryActions={allActions}\n * onAction={(id, action) => handleAction(id, action)}\n * />\n * ```\n */\nfunction CheckActionsComponent({\n checkId,\n primaryActions = [],\n secondaryActions = [],\n onAction,\n variant = \"combined\",\n size = \"small\",\n menuIcon,\n className,\n}: CheckActionsProps) {\n const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);\n const menuOpen = Boolean(anchorEl);\n\n const handleMenuClick = (event: MouseEvent<HTMLElement>) => {\n event.stopPropagation();\n setAnchorEl(event.currentTarget);\n };\n\n const handleMenuClose = () => {\n setAnchorEl(null);\n };\n\n const handleAction = (actionType: CheckActionType) => {\n handleMenuClose();\n onAction?.(checkId, actionType);\n };\n\n const renderActionButton = (action: CheckAction) => {\n const button = (\n <Button\n key={action.type}\n variant=\"outlined\"\n size={size}\n disabled={action.disabled}\n onClick={() => handleAction(action.type)}\n startIcon={action.icon}\n color={action.destructive ? \"error\" : \"inherit\"}\n >\n {action.label}\n </Button>\n );\n\n if (action.disabled && action.disabledTooltip) {\n return (\n <Tooltip key={action.type} title={action.disabledTooltip}>\n <span>{button}</span>\n </Tooltip>\n );\n }\n\n return button;\n };\n\n // Menu-only variant\n if (variant === \"menu\") {\n const allActions = [...primaryActions, ...secondaryActions];\n return (\n <Box className={className}>\n <IconButton onClick={handleMenuClick} size={size}>\n {menuIcon || <DefaultMenuIcon />}\n </IconButton>\n <Menu anchorEl={anchorEl} open={menuOpen} onClose={handleMenuClose}>\n {allActions.map((action) => (\n <MenuItem\n key={action.type}\n onClick={() => handleAction(action.type)}\n disabled={action.disabled}\n sx={{\n color: action.destructive ? \"error.main\" : \"inherit\",\n }}\n >\n {action.icon && (\n <Box component=\"span\" sx={{ mr: 1, display: \"flex\" }}>\n {action.icon}\n </Box>\n )}\n {action.label}\n </MenuItem>\n ))}\n </Menu>\n </Box>\n );\n }\n\n // Buttons-only variant\n if (variant === \"buttons\") {\n return (\n <Box className={className} sx={{ display: \"flex\", gap: 1 }}>\n <ButtonGroup size={size} variant=\"outlined\">\n {primaryActions.map(renderActionButton)}\n </ButtonGroup>\n </Box>\n );\n }\n\n // Combined variant (buttons + menu)\n return (\n <Box className={className} sx={{ display: \"flex\", gap: 1 }}>\n {/* Primary action buttons */}\n {primaryActions.length > 0 && (\n <ButtonGroup size={size} variant=\"outlined\">\n {primaryActions.map(renderActionButton)}\n </ButtonGroup>\n )}\n\n {/* Secondary actions menu */}\n {secondaryActions.length > 0 && (\n <>\n <IconButton onClick={handleMenuClick} size={size}>\n {menuIcon || <DefaultMenuIcon />}\n </IconButton>\n <Menu anchorEl={anchorEl} open={menuOpen} onClose={handleMenuClose}>\n {secondaryActions.map((action) => (\n <MenuItem\n key={action.type}\n onClick={() => handleAction(action.type)}\n disabled={action.disabled}\n sx={{\n color: action.destructive ? \"error.main\" : \"inherit\",\n }}\n >\n {action.icon && (\n <Box component=\"span\" sx={{ mr: 1, display: \"flex\" }}>\n {action.icon}\n </Box>\n )}\n {action.label}\n </MenuItem>\n ))}\n </Menu>\n </>\n )}\n </Box>\n );\n}\n\nexport const CheckActions = memo(CheckActionsComponent);\nCheckActions.displayName = \"CheckActions\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport TextField from \"@mui/material/TextField\";\nimport {\n type ChangeEvent,\n memo,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\n/**\n * Props for the CheckBreadcrumb component\n */\nexport interface CheckBreadcrumbProps {\n /** Current name value */\n name: string;\n /** Callback when name is saved */\n onNameChange?: (name: string) => void;\n /** Placeholder text when empty */\n placeholder?: string;\n /** Whether editing is disabled */\n disabled?: boolean;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CheckBreadcrumb Component\n *\n * A pure presentation component for displaying and editing a check name inline.\n * Supports click-to-edit behavior with Enter to save and Escape to cancel.\n * Also commits changes when clicking outside the input.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckBreadcrumb } from '@datarecce/ui/primitives';\n *\n * function CheckHeader({ check }) {\n * return (\n * <CheckBreadcrumb\n * name={check.name}\n * onNameChange={(name) => updateCheck(check.id, { name })}\n * />\n * );\n * }\n * ```\n *\n * @example Disabled state\n * ```tsx\n * <CheckBreadcrumb\n * name={check.name}\n * disabled\n * placeholder=\"Unnamed check\"\n * />\n * ```\n *\n * @example With placeholder\n * ```tsx\n * <CheckBreadcrumb\n * name=\"\"\n * placeholder=\"Enter check name...\"\n * onNameChange={(name) => updateCheck(check.id, { name })}\n * />\n * ```\n */\nfunction CheckBreadcrumbComponent({\n name,\n onNameChange,\n placeholder = \"Unnamed check\",\n disabled = false,\n className,\n}: CheckBreadcrumbProps) {\n const [isEditing, setIsEditing] = useState(false);\n const [editValue, setEditValue] = useState(name);\n const editInputRef = useRef<HTMLInputElement>(null);\n\n // Sync edit value when external name changes\n useEffect(() => {\n setEditValue(name);\n }, [name]);\n\n const handleClick = useCallback(() => {\n if (!disabled) {\n setEditValue(name);\n setIsEditing(true);\n }\n }, [disabled, name]);\n\n const handleCommit = useCallback(() => {\n const trimmedValue = editValue.trim();\n if (trimmedValue && trimmedValue !== name) {\n onNameChange?.(trimmedValue);\n }\n setIsEditing(false);\n }, [editValue, name, onNameChange]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleCommit();\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n setEditValue(name);\n setIsEditing(false);\n }\n },\n [handleCommit, name],\n );\n\n const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {\n setEditValue(event.target.value);\n }, []);\n\n // Handle click outside to commit changes\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n editInputRef.current &&\n !editInputRef.current.contains(event.target as Node | null)\n ) {\n handleCommit();\n }\n };\n\n if (isEditing) {\n document.addEventListener(\"mousedown\", handleClickOutside);\n }\n\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n };\n }, [isEditing, handleCommit]);\n\n // Focus input when entering edit mode\n useEffect(() => {\n if (isEditing && editInputRef.current) {\n editInputRef.current.focus();\n editInputRef.current.select();\n }\n }, [isEditing]);\n\n return (\n <Box\n className={className}\n sx={{\n flex: \"2\",\n fontSize: \"1rem\",\n fontWeight: 500,\n overflow: \"hidden\",\n color: \"text.primary\",\n cursor: disabled ? \"default\" : \"pointer\",\n }}\n >\n {isEditing ? (\n <TextField\n inputRef={editInputRef}\n value={editValue}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n size=\"small\"\n sx={{ width: \"100%\" }}\n variant=\"outlined\"\n />\n ) : (\n <Box\n sx={{\n flex: \"0 1 auto\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n \"&:hover\": {\n textDecoration: disabled ? \"none\" : \"underline\",\n },\n }}\n onClick={handleClick}\n >\n {name || (\n <Box\n component=\"span\"\n sx={{ color: \"text.secondary\", fontStyle: \"italic\" }}\n >\n {placeholder}\n </Box>\n )}\n </Box>\n )}\n </Box>\n );\n}\n\nexport const CheckBreadcrumb = memo(CheckBreadcrumbComponent);\nCheckBreadcrumb.displayName = \"CheckBreadcrumb\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Checkbox from \"@mui/material/Checkbox\";\nimport Chip from \"@mui/material/Chip\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport { type MouseEvent, memo } from \"react\";\n\n/**\n * Check type categories for icon display\n */\nexport type CheckType =\n | \"query\"\n | \"query_base\"\n | \"query_diff\"\n | \"schema_diff\"\n | \"lineage_diff\"\n | \"profile\"\n | \"profile_diff\"\n | \"row_count\"\n | \"row_count_diff\"\n | \"value_diff\"\n | \"histogram_diff\"\n | \"top_k_diff\"\n | \"simple\";\n\n/**\n * Status of a check run\n */\nexport type CheckRunStatus = \"pending\" | \"running\" | \"success\" | \"error\";\n\n/**\n * Data structure for check display\n */\nexport interface CheckCardData {\n /** Unique check identifier */\n id: string;\n /** Display name of the check */\n name: string;\n /** Type of check for icon display */\n type: CheckType;\n /** Whether the check is approved */\n isApproved?: boolean;\n /** Run status of the check */\n runStatus?: CheckRunStatus;\n /** Whether the check is a preset */\n isPreset?: boolean;\n}\n\n/**\n * Props for the CheckCard component\n */\nexport interface CheckCardProps {\n /** Check data to display */\n check: CheckCardData;\n /** Whether this card is currently selected */\n isSelected?: boolean;\n /** Callback when card is clicked */\n onClick?: (checkId: string) => void;\n /** Callback when approval checkbox is toggled */\n onApprovalChange?: (checkId: string, isApproved: boolean) => void;\n /** Whether approval checkbox is disabled */\n disableApproval?: boolean;\n /** Tooltip text for disabled approval */\n disabledApprovalTooltip?: string;\n /** Whether the entire card is disabled */\n disabled?: boolean;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * Get icon symbol for check type\n */\nfunction getCheckTypeIcon(type: CheckType): string {\n const icons: Record<CheckType, string> = {\n query: \"Q\",\n query_base: \"Q\",\n query_diff: \"QD\",\n schema_diff: \"SD\",\n lineage_diff: \"LD\",\n profile: \"P\",\n profile_diff: \"PD\",\n row_count: \"R\",\n row_count_diff: \"RD\",\n value_diff: \"VD\",\n histogram_diff: \"H\",\n top_k_diff: \"TK\",\n simple: \"•\",\n };\n return icons[type] || \"?\";\n}\n\n/**\n * Get color for check type icon\n */\nfunction getCheckTypeColor(type: CheckType): string {\n const colors: Record<CheckType, string> = {\n query: \"#3b82f6\", // blue\n query_base: \"#3b82f6\", // blue\n query_diff: \"#8b5cf6\", // purple\n schema_diff: \"#06b6d4\", // cyan\n lineage_diff: \"#10b981\", // emerald\n profile: \"#f59e0b\", // amber\n profile_diff: \"#f97316\", // orange\n row_count: \"#ec4899\", // pink\n row_count_diff: \"#ef4444\", // red\n value_diff: \"#6366f1\", // indigo\n histogram_diff: \"#14b8a6\", // teal\n top_k_diff: \"#a855f7\", // violet\n simple: \"#6b7280\", // gray\n };\n return colors[type] || \"#6b7280\";\n}\n\n/**\n * Get status color for run status\n * Note: Prefixed with underscore as it's reserved for future use\n */\nfunction _getStatusColor(status?: CheckRunStatus): string {\n if (!status) return \"transparent\";\n const colors: Record<CheckRunStatus, string> = {\n pending: \"#6b7280\", // gray\n running: \"#3b82f6\", // blue\n success: \"#22c55e\", // green\n error: \"#ef4444\", // red\n };\n return colors[status];\n}\n\n/**\n * Get status label for tooltip\n * Note: Prefixed with underscore as it's reserved for future use\n */\nfunction _getStatusLabel(status?: CheckRunStatus): string {\n if (!status) return \"\";\n const labels: Record<CheckRunStatus, string> = {\n pending: \"Pending\",\n running: \"Running\",\n success: \"Ready\",\n error: \"Error\",\n };\n return labels[status];\n}\n\n/**\n * CheckCard Component\n *\n * A pure presentation component for displaying a single check in a list.\n * Shows check type icon, name, approval status, and run status.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckCard } from '@datarecce/ui/primitives';\n *\n * function CheckListItem({ check }) {\n * return (\n * <CheckCard\n * check={{\n * id: check.check_id,\n * name: check.name,\n * type: check.type,\n * isApproved: check.is_checked,\n * }}\n * isSelected={selectedId === check.check_id}\n * onClick={(id) => setSelectedId(id)}\n * onApprovalChange={(id, approved) => updateCheck(id, { is_checked: approved })}\n * />\n * );\n * }\n * ```\n *\n * @example Disabled state\n * ```tsx\n * <CheckCard\n * check={check}\n * disableApproval\n * disabledApprovalTooltip=\"Run the check first to enable approval\"\n * />\n * ```\n */\nfunction CheckCardComponent({\n check,\n isSelected = false,\n onClick,\n onApprovalChange,\n disableApproval = false,\n disabledApprovalTooltip,\n disabled = false,\n className,\n}: CheckCardProps) {\n const handleClick = () => {\n if (!disabled && onClick) {\n onClick(check.id);\n }\n };\n\n const handleApprovalClick = (e: MouseEvent) => {\n e.stopPropagation();\n };\n\n const handleApprovalChange = (\n _e: React.ChangeEvent<HTMLInputElement>,\n checked: boolean,\n ) => {\n if (onApprovalChange) {\n onApprovalChange(check.id, checked);\n }\n };\n\n const approvalCheckbox = (\n <Checkbox\n checked={check.isApproved ?? false}\n onChange={handleApprovalChange}\n onClick={handleApprovalClick}\n disabled={disableApproval || disabled}\n size=\"small\"\n sx={{\n padding: \"4px\",\n \"&.Mui-disabled\": {\n opacity: 0.5,\n },\n }}\n />\n );\n\n return (\n <Box\n className={className}\n onClick={handleClick}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 1,\n padding: \"8px 12px\",\n cursor: disabled ? \"default\" : \"pointer\",\n borderLeft: isSelected ? \"3px solid\" : \"3px solid transparent\",\n borderLeftColor: isSelected ? \"primary.main\" : \"transparent\",\n backgroundColor: isSelected ? \"action.selected\" : \"transparent\",\n opacity: disabled ? 0.6 : 1,\n transition: \"background-color 0.15s ease\",\n \"&:hover\": {\n backgroundColor: disabled\n ? \"transparent\"\n : isSelected\n ? \"action.selected\"\n : \"action.hover\",\n },\n }}\n >\n {/* Type icon */}\n <Chip\n label={getCheckTypeIcon(check.type)}\n size=\"small\"\n sx={{\n minWidth: 32,\n height: 24,\n fontSize: \"0.7rem\",\n fontWeight: 600,\n backgroundColor: `${getCheckTypeColor(check.type)}20`,\n color: getCheckTypeColor(check.type),\n \"& .MuiChip-label\": {\n px: 1,\n },\n }}\n />\n\n {/* Check name */}\n <Typography\n variant=\"body2\"\n sx={{\n flexGrow: 1,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {check.name}\n </Typography>\n\n {/* Run status indicator */}\n {/*{check.runStatus && (*/}\n {/* <Tooltip title={getStatusLabel(check.runStatus)}>*/}\n {/* <Box*/}\n {/* sx={{*/}\n {/* width: 8,*/}\n {/* height: 8,*/}\n {/* borderRadius: \"50%\",*/}\n {/* backgroundColor: getStatusColor(check.runStatus),*/}\n {/* flexShrink: 0,*/}\n {/* }}*/}\n {/* />*/}\n {/* </Tooltip>*/}\n {/*)}*/}\n\n {/* Preset badge */}\n {check.isPreset && (\n <Chip\n label=\"Preset\"\n size=\"small\"\n variant=\"outlined\"\n sx={{\n height: 20,\n fontSize: \"0.65rem\",\n }}\n />\n )}\n\n {/* Approval checkbox */}\n {disableApproval && disabledApprovalTooltip ? (\n <Tooltip title={disabledApprovalTooltip}>\n <span>{approvalCheckbox}</span>\n </Tooltip>\n ) : (\n approvalCheckbox\n )}\n </Box>\n );\n}\n\nexport const CheckCard = memo(CheckCardComponent);\nCheckCard.displayName = \"CheckCard\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Link from \"@mui/material/Link\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport Typography from \"@mui/material/Typography\";\nimport {\n type KeyboardEvent,\n memo,\n useCallback,\n useEffect,\n useState,\n} from \"react\";\n\n/**\n * Props for the CheckDescription component\n */\nexport interface CheckDescriptionProps {\n /** Current description value */\n value?: string;\n /** Callback when description is saved */\n onChange?: (value?: string) => void;\n /** Placeholder text when empty */\n placeholder?: string;\n /** Whether editing is disabled */\n disabled?: boolean;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CheckDescription Component\n *\n * A pure presentation component for displaying and editing check descriptions.\n * Supports click-to-edit with Cmd+Enter (Mac) or Ctrl+Enter (Windows) to save.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckDescription } from '@datarecce/ui/primitives';\n *\n * function CheckDetail({ check }) {\n * return (\n * <CheckDescription\n * value={check.description}\n * onChange={(desc) => updateCheck(check.id, { description: desc })}\n * placeholder=\"Add a description...\"\n * />\n * );\n * }\n * ```\n *\n * @example Disabled state\n * ```tsx\n * <CheckDescription\n * value={check.description}\n * disabled\n * placeholder=\"Description not available\"\n * />\n * ```\n */\nfunction CheckDescriptionComponent({\n value,\n onChange,\n placeholder = \"Add description here\",\n disabled = false,\n className,\n}: CheckDescriptionProps) {\n const [isEditing, setIsEditing] = useState(false);\n const [editValue, setEditValue] = useState(value ?? \"\");\n\n // Sync edit value when external value changes\n useEffect(() => {\n setEditValue(value ?? \"\");\n }, [value]);\n\n const handleStartEdit = useCallback(() => {\n if (!disabled) {\n setIsEditing(true);\n setEditValue(value ?? \"\");\n }\n }, [disabled, value]);\n\n const handleSave = useCallback(() => {\n const trimmedValue = editValue.trim();\n onChange?.(trimmedValue || undefined);\n setIsEditing(false);\n }, [editValue, onChange]);\n\n const handleCancel = useCallback(() => {\n setEditValue(value ?? \"\");\n setIsEditing(false);\n }, [value]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n // Cmd+Enter (Mac) or Ctrl+Enter (Windows) to save\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSave();\n }\n // Escape to cancel\n if (e.key === \"Escape\") {\n e.preventDefault();\n handleCancel();\n }\n },\n [handleSave, handleCancel],\n );\n\n // Editing mode\n if (isEditing) {\n return (\n <Box className={className} sx={{ height: \"100%\" }}>\n <TextField\n multiline\n fullWidth\n minRows={3}\n value={editValue}\n onChange={(e) => setEditValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n autoFocus\n sx={{\n \"& .MuiInputBase-root\": {\n fontSize: \"0.875rem\",\n },\n }}\n />\n <Stack direction=\"row\" spacing={1} sx={{ mt: 1 }}>\n <Button variant=\"contained\" size=\"small\" onClick={handleSave}>\n Update\n </Button>\n <Link\n component=\"button\"\n variant=\"body2\"\n onClick={handleCancel}\n sx={{ cursor: \"pointer\" }}\n >\n Cancel\n </Link>\n </Stack>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: \"block\", mt: 0.5 }}\n >\n {navigator.platform.includes(\"Mac\") ? \"⌘\" : \"Ctrl\"}+Enter to save,\n Escape to cancel\n </Typography>\n </Box>\n );\n }\n\n // View mode\n return (\n <Box\n className={className}\n onClick={handleStartEdit}\n sx={{\n height: \"100%\",\n overflow: \"auto\",\n cursor: disabled ? \"default\" : \"pointer\",\n padding: 1,\n borderRadius: 1,\n \"&:hover\": {\n backgroundColor: disabled ? \"transparent\" : \"action.hover\",\n },\n }}\n >\n {value ? (\n <Typography\n variant=\"body2\"\n sx={{\n whiteSpace: \"pre-wrap\",\n wordBreak: \"break-word\",\n }}\n >\n {value}\n </Typography>\n ) : (\n <Typography\n variant=\"body2\"\n sx={{\n color: \"text.secondary\",\n fontStyle: \"italic\",\n }}\n >\n {placeholder}\n </Typography>\n )}\n </Box>\n );\n}\n\nexport const CheckDescription = memo(CheckDescriptionComponent);\nCheckDescription.displayName = \"CheckDescription\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Divider from \"@mui/material/Divider\";\nimport Tab from \"@mui/material/Tab\";\nimport Tabs from \"@mui/material/Tabs\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode, useState } from \"react\";\n\nimport {\n type CheckAction,\n CheckActions,\n type CheckActionType,\n} from \"./CheckActions\";\nimport { CheckDescription } from \"./CheckDescription\";\n\n/**\n * Tab configuration for CheckDetail\n */\nexport interface CheckDetailTab {\n /** Unique tab identifier */\n id: string;\n /** Tab label */\n label: string;\n /** Tab content */\n content: ReactNode;\n}\n\n/**\n * Props for the CheckDetail component\n */\nexport interface CheckDetailProps {\n /** Check ID */\n checkId: string;\n /** Check name (displayed as header) */\n name: string;\n /** Check type for display */\n type: string;\n /** Check description */\n description?: string;\n /** Whether the check is approved */\n isApproved?: boolean;\n /** Tabs to display (result, query, etc.) */\n tabs?: CheckDetailTab[];\n /** Default selected tab ID */\n defaultTab?: string;\n /** Primary actions for the check */\n primaryActions?: CheckAction[];\n /** Secondary actions for the check */\n secondaryActions?: CheckAction[];\n /** Callback when an action is triggered */\n onAction?: (checkId: string, actionType: CheckActionType) => void;\n /** Callback when description changes */\n onDescriptionChange?: (description?: string) => void;\n /** Callback when name changes */\n onNameChange?: (name: string) => void;\n /** Whether editing is disabled */\n disabled?: boolean;\n /** Optional header content (breadcrumbs, etc.) */\n headerContent?: ReactNode;\n /** Optional sidebar content */\n sidebarContent?: ReactNode;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CheckDetail Component\n *\n * A pure presentation component for displaying detailed information\n * about a single check with tabs, actions, and editable fields.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckDetail } from '@datarecce/ui/primitives';\n *\n * function CheckPage({ check, run }) {\n * return (\n * <CheckDetail\n * checkId={check.id}\n * name={check.name}\n * type={check.type}\n * description={check.description}\n * isApproved={check.is_checked}\n * tabs={[\n * { id: 'result', label: 'Result', content: <ResultView run={run} /> },\n * { id: 'query', label: 'Query', content: <QueryView sql={check.params.sql} /> },\n * ]}\n * primaryActions={[\n * { type: 'run', label: 'Run' },\n * { type: 'approve', label: check.is_checked ? 'Approved' : 'Pending' },\n * ]}\n * secondaryActions={[\n * { type: 'delete', label: 'Delete', destructive: true },\n * ]}\n * onAction={(id, action) => handleAction(id, action)}\n * onDescriptionChange={(desc) => updateCheck(id, { description: desc })}\n * />\n * );\n * }\n * ```\n *\n * @example With sidebar\n * ```tsx\n * <CheckDetail\n * {...props}\n * sidebarContent={<CheckTimeline checkId={check.id} />}\n * />\n * ```\n */\nfunction CheckDetailComponent({\n checkId,\n name,\n type,\n description,\n isApproved = false,\n tabs = [],\n defaultTab,\n primaryActions = [],\n secondaryActions = [],\n onAction,\n onDescriptionChange,\n onNameChange,\n disabled = false,\n headerContent,\n sidebarContent,\n className,\n}: CheckDetailProps) {\n const [selectedTab, setSelectedTab] = useState(defaultTab ?? tabs[0]?.id);\n const [isEditingName, setIsEditingName] = useState(false);\n const [editedName, setEditedName] = useState(name);\n\n const handleNameClick = () => {\n if (!disabled) {\n setIsEditingName(true);\n setEditedName(name);\n }\n };\n\n const handleNameSave = () => {\n if (editedName.trim() && editedName !== name) {\n onNameChange?.(editedName.trim());\n }\n setIsEditingName(false);\n };\n\n const handleNameKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\") {\n handleNameSave();\n } else if (e.key === \"Escape\") {\n setEditedName(name);\n setIsEditingName(false);\n }\n };\n\n const selectedTabContent = tabs.find((t) => t.id === selectedTab)?.content;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n height: \"100%\",\n overflow: \"hidden\",\n }}\n >\n {/* Header */}\n <Box sx={{ p: 2, borderBottom: 1, borderColor: \"divider\" }}>\n {headerContent}\n\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"flex-start\",\n justifyContent: \"space-between\",\n gap: 2,\n }}\n >\n {/* Name */}\n <Box sx={{ flexGrow: 1, minWidth: 0 }}>\n {isEditingName ? (\n <input\n type=\"text\"\n value={editedName}\n onChange={(e) => setEditedName(e.target.value)}\n onBlur={handleNameSave}\n onKeyDown={handleNameKeyDown}\n autoFocus\n style={{\n fontSize: \"1.25rem\",\n fontWeight: 500,\n border: \"none\",\n borderBottom: \"2px solid\",\n borderColor: \"currentColor\",\n outline: \"none\",\n background: \"transparent\",\n width: \"100%\",\n }}\n />\n ) : (\n <Typography\n variant=\"h6\"\n onClick={handleNameClick}\n sx={{\n cursor: disabled ? \"default\" : \"pointer\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n \"&:hover\": {\n textDecoration: disabled ? \"none\" : \"underline\",\n },\n }}\n >\n {name}\n </Typography>\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {type} {isApproved && \"• Approved\"}\n </Typography>\n </Box>\n\n {/* Actions */}\n <CheckActions\n checkId={checkId}\n primaryActions={primaryActions}\n secondaryActions={secondaryActions}\n onAction={onAction}\n />\n </Box>\n </Box>\n\n {/* Main content area */}\n <Box sx={{ display: \"flex\", flexGrow: 1, overflow: \"hidden\" }}>\n {/* Main panel */}\n <Box\n sx={{\n flexGrow: 1,\n display: \"flex\",\n flexDirection: \"column\",\n overflow: \"hidden\",\n }}\n >\n {/* Description */}\n <Box sx={{ p: 2, borderBottom: 1, borderColor: \"divider\" }}>\n <Typography variant=\"subtitle2\" sx={{ mb: 1 }}>\n Description\n </Typography>\n <CheckDescription\n value={description}\n onChange={onDescriptionChange}\n disabled={disabled}\n />\n </Box>\n\n {/* Tabs */}\n {tabs.length > 0 && (\n <>\n <Box sx={{ borderBottom: 1, borderColor: \"divider\" }}>\n <Tabs\n value={selectedTab}\n onChange={(_e, newValue) => setSelectedTab(newValue)}\n >\n {tabs.map((tab) => (\n <Tab key={tab.id} value={tab.id} label={tab.label} />\n ))}\n </Tabs>\n </Box>\n\n {/* Tab content */}\n <Box sx={{ flexGrow: 1, overflow: \"auto\", p: 2 }}>\n {selectedTabContent}\n </Box>\n </>\n )}\n </Box>\n\n {/* Sidebar */}\n {sidebarContent && (\n <>\n <Divider orientation=\"vertical\" flexItem />\n <Box\n sx={{\n width: 350,\n flexShrink: 0,\n overflow: \"auto\",\n }}\n >\n {sidebarContent}\n </Box>\n </>\n )}\n </Box>\n </Box>\n );\n}\n\nexport const CheckDetail = memo(CheckDetailComponent);\nCheckDetail.displayName = \"CheckDetail\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode } from \"react\";\n\n/**\n * Props for the CheckEmptyState component\n */\nexport interface CheckEmptyStateProps {\n /** Main title text */\n title?: string;\n /** Description text */\n description?: string;\n /** Icon element to display */\n icon?: ReactNode;\n /** Primary action button text */\n actionText?: string;\n /** Callback when primary action is clicked */\n onAction?: () => void;\n /** Whether the action is loading */\n isLoading?: boolean;\n /** Optional helper text below the action */\n helperText?: string;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CheckEmptyState Component\n *\n * A pure presentation component for displaying an empty state\n * when no checks exist in the list.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckEmptyState } from '@datarecce/ui/primitives';\n *\n * function MyCheckList() {\n * if (checks.length === 0) {\n * return (\n * <CheckEmptyState\n * title=\"No checks yet\"\n * description=\"Create your first check to start validating data\"\n * actionText=\"Create Schema Diff Check\"\n * onAction={() => createSchemaDiffCheck()}\n * />\n * );\n * }\n * return <CheckList checks={checks} />;\n * }\n * ```\n *\n * @example Custom icon\n * ```tsx\n * import { TbChecklist } from 'react-icons/tb';\n *\n * <CheckEmptyState\n * icon={<TbChecklist size={48} />}\n * title=\"No checks found\"\n * description=\"Checks help you validate your data changes\"\n * />\n * ```\n */\nfunction CheckEmptyStateComponent({\n title = \"No checks yet\",\n description = \"Create your first check to start validating data changes\",\n icon,\n actionText,\n onAction,\n isLoading = false,\n helperText,\n className,\n}: CheckEmptyStateProps) {\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n textAlign: \"center\",\n padding: 4,\n minHeight: 300,\n }}\n >\n <Stack spacing={2} alignItems=\"center\">\n {/* Icon */}\n {icon && (\n <Box\n sx={{\n color: \"text.secondary\",\n fontSize: 48,\n mb: 1,\n }}\n >\n {icon}\n </Box>\n )}\n\n {/* Title */}\n <Typography\n variant=\"h6\"\n sx={{\n fontWeight: 500,\n color: \"text.primary\",\n }}\n >\n {title}\n </Typography>\n\n {/* Description */}\n <Typography\n variant=\"body2\"\n sx={{\n color: \"text.secondary\",\n maxWidth: 400,\n }}\n >\n {description}\n </Typography>\n\n {/* Action button */}\n {actionText && onAction && (\n <Button\n variant=\"contained\"\n onClick={onAction}\n disabled={isLoading}\n sx={{ mt: 2 }}\n >\n {isLoading ? \"Creating...\" : actionText}\n </Button>\n )}\n\n {/* Helper text */}\n {helperText && (\n <Typography\n variant=\"caption\"\n sx={{\n color: \"text.secondary\",\n mt: 1,\n }}\n >\n {helperText}\n </Typography>\n )}\n </Stack>\n </Box>\n );\n}\n\nexport const CheckEmptyState = memo(CheckEmptyStateComponent);\nCheckEmptyState.displayName = \"CheckEmptyState\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport List from \"@mui/material/List\";\nimport ListItem from \"@mui/material/ListItem\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo } from \"react\";\n\nimport { CheckCard, type CheckCardData } from \"./CheckCard\";\n\n/**\n * Props for the CheckList component\n */\nexport interface CheckListProps {\n /** Array of checks to display */\n checks: CheckCardData[];\n /** Currently selected check ID */\n selectedId?: string | null;\n /** Callback when a check is selected */\n onCheckSelect?: (checkId: string) => void;\n /** Callback when check approval status changes */\n onApprovalChange?: (checkId: string, isApproved: boolean) => void;\n /** Callback when checks are reordered (for drag-drop implementations) */\n onReorder?: (sourceIndex: number, destinationIndex: number) => void;\n /** Whether approval is disabled for all checks */\n disableApproval?: boolean;\n /** Tooltip for disabled approval */\n disabledApprovalTooltip?: string;\n /** Optional title for the list */\n title?: string;\n /** Optional CSS class name */\n className?: string;\n /** Whether the list is loading */\n isLoading?: boolean;\n /** Content to show when list is empty */\n emptyContent?: React.ReactNode;\n}\n\n/**\n * CheckList Component\n *\n * A pure presentation component for displaying a list of checks.\n * This component does not include drag-and-drop functionality -\n * implement that at the consumer level if needed.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckList } from '@datarecce/ui/primitives';\n *\n * function MyCheckList({ checks }) {\n * const [selectedId, setSelectedId] = useState(null);\n *\n * return (\n * <CheckList\n * checks={checks.map(c => ({\n * id: c.check_id,\n * name: c.name,\n * type: c.type,\n * isApproved: c.is_checked,\n * }))}\n * selectedId={selectedId}\n * onCheckSelect={setSelectedId}\n * onApprovalChange={(id, approved) => updateCheck(id, approved)}\n * />\n * );\n * }\n * ```\n *\n * @example With empty state\n * ```tsx\n * <CheckList\n * checks={[]}\n * emptyContent={<CheckEmptyState onCreateFirst={() => createCheck()} />}\n * />\n * ```\n */\nfunction CheckListComponent({\n checks,\n selectedId,\n onCheckSelect,\n onApprovalChange,\n disableApproval = false,\n disabledApprovalTooltip,\n title,\n className,\n isLoading = false,\n emptyContent,\n}: CheckListProps) {\n // Loading state\n if (isLoading) {\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: 200,\n }}\n >\n <Typography color=\"text.secondary\">Loading checks...</Typography>\n </Box>\n );\n }\n\n // Empty state\n if (checks.length === 0) {\n return (\n <Box className={className}>\n {title && (\n <Typography\n variant=\"subtitle2\"\n sx={{ px: 2, py: 1, color: \"text.secondary\" }}\n >\n {title}\n </Typography>\n )}\n {emptyContent || (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: 200,\n }}\n >\n <Typography color=\"text.secondary\">No checks</Typography>\n </Box>\n )}\n </Box>\n );\n }\n\n return (\n <Box className={className} sx={{ height: \"100%\", overflow: \"auto\" }}>\n {title && (\n <Typography\n variant=\"subtitle2\"\n sx={{ px: 2, py: 1, color: \"text.secondary\" }}\n >\n {title}\n </Typography>\n )}\n <List disablePadding>\n {checks.map((check) => (\n <ListItem key={check.id} disablePadding>\n <CheckCard\n check={check}\n isSelected={selectedId === check.id}\n onClick={onCheckSelect}\n onApprovalChange={onApprovalChange}\n disableApproval={disableApproval}\n disabledApprovalTooltip={disabledApprovalTooltip}\n />\n </ListItem>\n ))}\n </List>\n </Box>\n );\n}\n\nexport const CheckList = memo(CheckListComponent);\nCheckList.displayName = \"CheckList\";\n","\"use client\";\n\nimport {\n Background,\n Controls,\n type Edge,\n MiniMap,\n type Node,\n ReactFlow,\n useEdgesState,\n useNodesState,\n} from \"@xyflow/react\";\nimport \"@xyflow/react/dist/style.css\";\nimport Box from \"@mui/material/Box\";\nimport { useCallback } from \"react\";\n\nimport { LineageColumnNode } from \"./columns\";\nimport { LineageEdge, type LineageEdgeData } from \"./edges\";\nimport { LineageNode, type LineageNodeData } from \"./nodes\";\n\nexport interface LineageCanvasProps {\n /** Nodes to display */\n nodes: Node<LineageNodeData>[];\n /** Edges connecting nodes */\n edges: Edge<LineageEdgeData>[];\n /** Callback when node selection changes */\n onNodeSelect?: (nodeId: string | null) => void;\n /** Callback when node is double-clicked */\n onNodeDoubleClick?: (nodeId: string) => void;\n /** Whether to show minimap */\n showMiniMap?: boolean;\n /** Whether to show controls */\n showControls?: boolean;\n /** Whether to show background grid */\n showBackground?: boolean;\n /** Height of the graph container */\n height?: number | string;\n /** Whether the graph is interactive */\n interactive?: boolean;\n}\n\nconst nodeTypes = {\n lineageNode: LineageNode,\n lineageGraphColumnNode: LineageColumnNode,\n};\n\nconst edgeTypes = {\n lineageEdge: LineageEdge,\n};\n\nexport function LineageCanvas({\n nodes: initialNodes,\n edges: initialEdges,\n onNodeSelect,\n onNodeDoubleClick,\n showMiniMap = true,\n showControls = true,\n showBackground = true,\n height = 600,\n interactive = true,\n}: LineageCanvasProps) {\n const [nodes, _setNodes, onNodesChange] = useNodesState(initialNodes);\n const [edges, _setEdges, onEdgesChange] = useEdgesState(initialEdges);\n\n const handleNodeClick = useCallback(\n (_event: React.MouseEvent, node: Node) => {\n onNodeSelect?.(node.id);\n },\n [onNodeSelect],\n );\n\n const handlePaneClick = useCallback(() => {\n onNodeSelect?.(null);\n }, [onNodeSelect]);\n\n const handleNodeDoubleClick = useCallback(\n (_event: React.MouseEvent, node: Node) => {\n onNodeDoubleClick?.(node.id);\n },\n [onNodeDoubleClick],\n );\n\n return (\n <Box sx={{ width: \"100%\", height }}>\n <ReactFlow\n nodes={nodes}\n edges={edges}\n onNodesChange={interactive ? onNodesChange : undefined}\n onEdgesChange={interactive ? onEdgesChange : undefined}\n onNodeClick={handleNodeClick}\n onNodeDoubleClick={handleNodeDoubleClick}\n onPaneClick={handlePaneClick}\n nodeTypes={nodeTypes}\n edgeTypes={edgeTypes}\n fitView\n nodesDraggable={interactive}\n nodesConnectable={false}\n elementsSelectable={interactive}\n >\n {showBackground && <Background />}\n {showControls && <Controls />}\n {showMiniMap && (\n <MiniMap\n nodeColor={(node) => {\n const data = node.data as LineageNodeData;\n switch (data.changeStatus) {\n case \"added\":\n return \"#22c55e\";\n case \"removed\":\n return \"#ef4444\";\n case \"modified\":\n return \"#f59e0b\";\n default:\n return \"#94a3b8\";\n }\n }}\n />\n )}\n </ReactFlow>\n </Box>\n );\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport Typography from \"@mui/material/Typography\";\nimport type { Edge, Node } from \"@xyflow/react\";\nimport { toPng } from \"html-to-image\";\nimport {\n forwardRef,\n type Ref,\n useCallback,\n useImperativeHandle,\n useMemo,\n useRef,\n} from \"react\";\n\nimport { useLineageGraphContext } from \"../../contexts/lineage\";\nimport type {\n LineageGraph,\n LineageGraphEdge,\n LineageGraphNode,\n} from \"../../contexts/lineage/types\";\nimport {\n selectDownstream,\n selectUpstream,\n toReactFlowBasic,\n} from \"../../contexts/lineage/utils\";\nimport type { LineageEdgeData } from \"./edges\";\nimport { LineageCanvas } from \"./LineageCanvas\";\nimport type { LineageNodeData } from \"./nodes\";\n\n/**\n * Props for the LineageView component.\n * Defines options for viewing lineage diff data.\n */\nexport interface LineageViewProps {\n /**\n * Optional lineage graph data. If not provided, uses LineageGraphContext.\n */\n lineageGraph?: LineageGraph;\n\n /**\n * View options for lineage diff visualization\n */\n viewOptions?: {\n view_mode?: \"changed_models\" | \"all\";\n node_ids?: string[];\n select?: string;\n exclude?: string;\n packages?: string[];\n column_level_lineage?: {\n node_id?: string;\n column?: string;\n change_analysis?: boolean;\n };\n };\n\n /**\n * Whether the view allows user interaction\n * @default true\n */\n interactive?: boolean;\n\n /**\n * Optional height for the view\n * @default 600\n */\n height?: number | string;\n\n /**\n * Optional filter function for nodes\n */\n filterNodes?: (key: string, node: LineageGraphNode) => boolean;\n\n /**\n * Callback when a node is selected\n */\n onNodeSelect?: (nodeId: string | null) => void;\n\n /**\n * Callback when a node is double-clicked\n */\n onNodeDoubleClick?: (nodeId: string) => void;\n\n /**\n * Optional dagre instance for layout.\n * If not provided, nodes will be positioned at (0,0).\n * Install @dagrejs/dagre and pass the imported module.\n */\n // biome-ignore lint/suspicious/noExplicitAny: dagre is external dependency\n dagre?: any;\n\n /**\n * Whether to show the minimap\n * @default true\n */\n showMiniMap?: boolean;\n\n /**\n * Whether to show the controls\n * @default true\n */\n showControls?: boolean;\n\n /**\n * Whether to show the background grid\n * @default true\n */\n showBackground?: boolean;\n}\n\n/**\n * Ref interface for LineageView component.\n * Provides methods to interact with the LineageView programmatically.\n */\nexport interface LineageViewRef {\n /**\n * Copies the current lineage view as an image to the clipboard\n */\n copyToClipboard: () => Promise<void>;\n}\n\n/**\n * LineageView Component\n *\n * A high-level component for visualizing data lineage graphs using React Flow.\n * Shows relationships between models and their change status.\n *\n * Can receive data from:\n * 1. LineageGraphContext (wrap with LineageGraphProvider)\n * 2. Direct prop (pass lineageGraph prop)\n *\n * For auto-layout, provide a dagre instance:\n *\n * @example Using with context\n * ```tsx\n * import { LineageGraphProvider, LineageView } from '@datarecce/ui';\n * import dagre from '@dagrejs/dagre';\n *\n * function App() {\n * return (\n * <LineageGraphProvider serverInfo={serverInfo}>\n * <LineageView dagre={dagre} />\n * </LineageGraphProvider>\n * );\n * }\n * ```\n *\n * @example Using with direct data\n * ```tsx\n * import { buildLineageGraph, LineageView } from '@datarecce/ui';\n * import dagre from '@dagrejs/dagre';\n *\n * function App({ serverInfo }) {\n * const lineageGraph = buildLineageGraph(\n * serverInfo.lineage.base,\n * serverInfo.lineage.current,\n * serverInfo.lineage.diff\n * );\n *\n * return <LineageView lineageGraph={lineageGraph} dagre={dagre} />;\n * }\n * ```\n */\nexport const LineageView = forwardRef<LineageViewRef, LineageViewProps>(\n function LineageView(props: LineageViewProps, ref: Ref<LineageViewRef>) {\n const {\n lineageGraph: propLineageGraph,\n viewOptions,\n interactive = true,\n height = 600,\n filterNodes,\n onNodeSelect,\n onNodeDoubleClick,\n dagre,\n showMiniMap = true,\n showControls = true,\n showBackground = true,\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Get lineage graph from context or props\n const contextValue = useLineageGraphContext();\n const lineageGraph = propLineageGraph ?? contextValue.lineageGraph;\n const isLoading = !propLineageGraph && contextValue.isLoading;\n const error = !propLineageGraph ? contextValue.error : undefined;\n\n // Apply filtering and convert to React Flow format\n const { nodes, edges } = useMemo((): {\n nodes: Node<LineageNodeData>[];\n edges: Edge<LineageEdgeData>[];\n } => {\n if (!lineageGraph) {\n return { nodes: [], edges: [] };\n }\n\n // Determine which nodes to include\n let selectedNodeIds: string[] | undefined;\n\n if (viewOptions?.node_ids && viewOptions.node_ids.length > 0) {\n // Explicit node selection\n selectedNodeIds = viewOptions.node_ids;\n } else if (viewOptions?.view_mode === \"changed_models\") {\n // Only changed models\n selectedNodeIds = lineageGraph.modifiedSet;\n }\n\n // Apply select/exclude patterns (simplified - expand upstream/downstream)\n if (viewOptions?.select && selectedNodeIds) {\n // Simple pattern: +upstream, +downstream\n if (viewOptions.select.includes(\"+\")) {\n const upstream = selectUpstream(lineageGraph, selectedNodeIds);\n const downstream = selectDownstream(lineageGraph, selectedNodeIds);\n selectedNodeIds = [...new Set([...upstream, ...downstream])];\n }\n }\n\n // Apply package filter\n if (viewOptions?.packages && viewOptions.packages.length > 0) {\n const packageSet = new Set(viewOptions.packages);\n const allNodeIds = selectedNodeIds ?? Object.keys(lineageGraph.nodes);\n selectedNodeIds = allNodeIds.filter((id) => {\n const node = lineageGraph.nodes[id];\n return (\n node?.data.packageName && packageSet.has(node.data.packageName)\n );\n });\n }\n\n // Apply custom filter\n if (filterNodes) {\n const allNodeIds = selectedNodeIds ?? Object.keys(lineageGraph.nodes);\n selectedNodeIds = allNodeIds.filter((id) => {\n const node = lineageGraph.nodes[id];\n return node ? filterNodes(id, node) : false;\n });\n }\n\n // Convert to React Flow format (internal types)\n const [rfNodes, rfEdges] = toReactFlowBasic(\n lineageGraph,\n selectedNodeIds,\n );\n\n // Apply dagre layout if provided\n if (dagre && rfNodes.length > 0) {\n applyDagreLayout(dagre, rfNodes, rfEdges);\n }\n\n // Convert to presentation types (LineageNodeData / LineageEdgeData)\n const presentationNodes: Node<LineageNodeData>[] = rfNodes.map(\n (node) => ({\n id: node.id,\n position: node.position,\n type: \"lineageNode\",\n data: {\n label: node.data.name,\n nodeType: node.data.resourceType,\n changeStatus: node.data.changeStatus,\n resourceType: node.data.resourceType,\n packageName: node.data.packageName,\n },\n }),\n );\n\n const presentationEdges: Edge<LineageEdgeData>[] = rfEdges.map(\n (edge) => ({\n id: edge.id,\n source: edge.source,\n target: edge.target,\n type: \"lineageEdge\",\n data: {\n changeStatus: edge.data?.changeStatus,\n },\n }),\n );\n\n return {\n nodes: presentationNodes,\n edges: presentationEdges,\n };\n }, [lineageGraph, viewOptions, filterNodes, dagre]);\n\n // Copy to clipboard implementation\n const copyToClipboard = useCallback(async () => {\n if (!containerRef.current) {\n return;\n }\n\n try {\n const dataUrl = await toPng(containerRef.current, {\n backgroundColor: \"#ffffff\",\n pixelRatio: 2,\n });\n\n const response = await fetch(dataUrl);\n const blob = await response.blob();\n\n await navigator.clipboard.write([\n new ClipboardItem({\n [blob.type]: blob,\n }),\n ]);\n } catch (err) {\n console.error(\"Failed to copy to clipboard:\", err);\n throw err;\n }\n }, []);\n\n // Expose ref methods\n useImperativeHandle(\n ref,\n () => ({\n copyToClipboard,\n }),\n [copyToClipboard],\n );\n\n // Loading state\n if (isLoading) {\n return (\n <Box\n sx={{\n width: \"100%\",\n height,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <CircularProgress />\n </Box>\n );\n }\n\n // Error state\n if (error) {\n return (\n <Box\n sx={{\n width: \"100%\",\n height,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Typography color=\"error\">{error}</Typography>\n </Box>\n );\n }\n\n // No data state\n if (!lineageGraph) {\n return (\n <Box\n sx={{\n width: \"100%\",\n height,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Typography color=\"text.secondary\">\n No lineage data available. Provide data via LineageGraphProvider or\n lineageGraph prop.\n </Typography>\n </Box>\n );\n }\n\n // Empty after filtering\n if (nodes.length === 0) {\n return (\n <Box\n sx={{\n width: \"100%\",\n height,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Typography color=\"text.secondary\">\n No nodes match the current filter criteria.\n </Typography>\n </Box>\n );\n }\n\n return (\n <Box ref={containerRef} sx={{ width: \"100%\", height }}>\n <LineageCanvas\n nodes={nodes}\n edges={edges}\n onNodeSelect={onNodeSelect}\n onNodeDoubleClick={onNodeDoubleClick}\n showMiniMap={showMiniMap}\n showControls={showControls}\n showBackground={showBackground}\n height=\"100%\"\n interactive={interactive}\n />\n </Box>\n );\n },\n);\n\n/**\n * Apply dagre layout to nodes and edges (internal helper)\n */\nfunction applyDagreLayout(\n // biome-ignore lint/suspicious/noExplicitAny: dagre is external dependency\n dagre: any,\n nodes: LineageGraphNode[],\n edges: LineageGraphEdge[],\n direction = \"LR\",\n): void {\n const dagreGraph = new dagre.graphlib.Graph();\n dagreGraph.setDefaultEdgeLabel(() => ({}));\n dagreGraph.setGraph({ rankdir: direction, ranksep: 50, nodesep: 30 });\n\n for (const node of nodes) {\n const width = node.style?.width ? Number(node.style.width) : 300;\n const height = node.style?.height ? Number(node.style.height) : 60;\n dagreGraph.setNode(node.id, { width, height });\n }\n\n for (const edge of edges) {\n dagreGraph.setEdge(edge.source, edge.target);\n }\n\n dagre.layout(dagreGraph);\n\n for (const node of nodes) {\n const nodeWidth = node.style?.width ? Number(node.style.width) : 300;\n const nodeHeight = node.style?.height ? Number(node.style.height) : 60;\n const nodeWithPosition = dagreGraph.node(node.id);\n\n if (nodeWithPosition) {\n node.position = {\n x: nodeWithPosition.x - nodeWidth / 2,\n y: nodeWithPosition.y - nodeHeight / 2,\n };\n }\n }\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport { ReactFlowProvider } from \"@xyflow/react\";\nimport { forwardRef, type Ref } from \"react\";\n\nimport { LineageView, type LineageViewRef } from \"../lineage/LineageView\";\n\n/**\n * View options for lineage diff checks\n */\nexport interface LineageDiffViewOptions {\n view_mode?: \"changed_models\" | \"all\";\n node_ids?: string[];\n select?: string;\n exclude?: string;\n packages?: string[];\n column_level_lineage?: {\n node_id?: string;\n column?: string;\n change_analysis?: boolean;\n };\n}\n\n/**\n * Props for the LineageDiffView component\n */\nexport interface LineageDiffViewProps {\n /**\n * Parameters from the check, merged with view options\n */\n params?: Record<string, unknown>;\n\n /**\n * View options for the lineage display\n */\n viewOptions?: LineageDiffViewOptions;\n\n /**\n * Whether the view is interactive (allows user input)\n * @default false\n */\n interactive?: boolean;\n\n /**\n * Optional height for the view\n */\n height?: number | string;\n\n /**\n * Optional dagre instance for layout\n */\n // biome-ignore lint/suspicious/noExplicitAny: dagre is external dependency\n dagre?: any;\n}\n\n/**\n * LineageDiffView Component\n *\n * A presentation component for displaying lineage diff results within a check.\n * Wraps the LineageView component with ReactFlowProvider and handles\n * merging of check params with view options.\n *\n * @example Basic usage\n * ```tsx\n * import { LineageDiffView } from '@datarecce/ui/primitives';\n * import dagre from '@dagrejs/dagre';\n *\n * function CheckLineageResult({ check }) {\n * return (\n * <LineageDiffView\n * params={check.params}\n * viewOptions={check.view_options}\n * dagre={dagre}\n * />\n * );\n * }\n * ```\n *\n * @example With ref for clipboard\n * ```tsx\n * const lineageRef = useRef<LineageViewRef>(null);\n *\n * const handleCopy = () => {\n * lineageRef.current?.copyToClipboard();\n * };\n *\n * <LineageDiffView\n * ref={lineageRef}\n * params={check.params}\n * viewOptions={check.view_options}\n * />\n * ```\n */\nfunction LineageDiffViewComponent(\n {\n params,\n viewOptions,\n interactive = false,\n height,\n dagre,\n }: LineageDiffViewProps,\n ref: Ref<LineageViewRef>,\n) {\n // Merge params with view options - params take precedence\n const mergedViewOptions = {\n ...viewOptions,\n ...params,\n };\n\n return (\n <Box\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n height: height ?? \"100%\",\n }}\n >\n <ReactFlowProvider>\n <LineageView\n viewOptions={mergedViewOptions}\n interactive={interactive}\n ref={ref}\n dagre={dagre}\n />\n </ReactFlowProvider>\n </Box>\n );\n}\n\nexport const LineageDiffView = forwardRef<LineageViewRef, LineageDiffViewProps>(\n LineageDiffViewComponent,\n);\nLineageDiffView.displayName = \"LineageDiffView\";\n\n// Re-export LineageViewRef for convenience\nexport type { LineageViewRef };\n","\"use client\";\n\nimport { PostgreSQL, sql } from \"@codemirror/lang-sql\";\nimport { yaml } from \"@codemirror/lang-yaml\";\nimport { Prec } from \"@codemirror/state\";\nimport { EditorView, keymap } from \"@codemirror/view\";\nimport { githubDark, githubLight } from \"@uiw/codemirror-theme-github\";\nimport CodeMirror from \"@uiw/react-codemirror\";\nimport { useMemo } from \"react\";\n\n/**\n * Supported languages for the code editor\n */\nexport type CodeEditorLanguage = \"sql\" | \"yaml\" | \"text\";\n\n/**\n * Theme options for the code editor\n */\nexport type CodeEditorTheme = \"light\" | \"dark\";\n\n/**\n * Props for the CodeEditor component\n */\nexport interface CodeEditorProps {\n /** The code content to display */\n value: string;\n /** Callback when content changes */\n onChange?: (value: string) => void;\n /** Language for syntax highlighting */\n language?: CodeEditorLanguage;\n /** Whether editor is read-only */\n readOnly?: boolean;\n /** Show line numbers */\n lineNumbers?: boolean;\n /** Enable word wrap */\n wordWrap?: boolean;\n /** Font size in pixels */\n fontSize?: number;\n /** Editor height */\n height?: string;\n /** Optional CSS class */\n className?: string;\n /** Theme mode */\n theme?: CodeEditorTheme;\n /** Custom keyboard shortcuts */\n keyBindings?: Array<{ key: string; run: () => boolean }>;\n}\n\n/**\n * Get language extension for CodeMirror\n */\nfunction getLanguageExtension(language: CodeEditorLanguage) {\n switch (language) {\n case \"sql\":\n return sql({ dialect: PostgreSQL });\n case \"yaml\":\n return yaml();\n default:\n return null;\n }\n}\n\n/**\n * CodeEditor Component\n *\n * A code editor component using CodeMirror with React integration.\n * Supports SQL and YAML syntax highlighting with customizable theming.\n *\n * @example Basic usage\n * ```tsx\n * import { CodeEditor } from '@datarecce/ui/primitives';\n *\n * function SqlEditor({ sql, onSqlChange }) {\n * return (\n * <CodeEditor\n * value={sql}\n * onChange={onSqlChange}\n * language=\"sql\"\n * />\n * );\n * }\n * ```\n *\n * @example Read-only with dark theme\n * ```tsx\n * <CodeEditor\n * value={yamlContent}\n * language=\"yaml\"\n * readOnly\n * theme=\"dark\"\n * height=\"300px\"\n * />\n * ```\n *\n * @example With custom key bindings\n * ```tsx\n * const keyBindings = [\n * { key: \"Mod-Enter\", run: () => { handleSubmit(); return true; } }\n * ];\n *\n * <CodeEditor\n * value={sql}\n * onChange={setSql}\n * language=\"sql\"\n * keyBindings={keyBindings}\n * />\n * ```\n */\nexport function CodeEditor({\n value,\n onChange,\n language = \"sql\",\n readOnly = false,\n lineNumbers = true,\n wordWrap = true,\n fontSize = 14,\n height = \"100%\",\n className = \"\",\n theme = \"light\",\n keyBindings = [],\n}: CodeEditorProps) {\n const extensions = useMemo(() => {\n const exts = [\n EditorView.theme({\n \"&\": { fontSize: `${fontSize}px` },\n \".cm-content\": {\n fontFamily: \"'JetBrains Mono', 'Fira Code', monospace\",\n },\n \".cm-gutters\": {\n fontFamily: \"'JetBrains Mono', 'Fira Code', monospace\",\n },\n }),\n ];\n\n // Add language extension if not plain text\n const langExt = getLanguageExtension(language);\n if (langExt) {\n exts.push(langExt);\n }\n\n if (wordWrap) {\n exts.push(EditorView.lineWrapping);\n }\n\n // Use Prec.highest to ensure custom keybindings take precedence\n // over defaultKeymap bindings (e.g., Mod-Enter -> insertBlankLine)\n if (keyBindings.length > 0) {\n exts.push(Prec.highest(keymap.of(keyBindings)));\n }\n\n return exts;\n }, [language, fontSize, wordWrap, keyBindings]);\n\n const themeExtension = useMemo(() => {\n return theme === \"dark\" ? githubDark : githubLight;\n }, [theme]);\n\n const handleChange = (val: string) => {\n if (onChange) {\n onChange(val);\n }\n };\n\n return (\n <CodeMirror\n value={value}\n onChange={handleChange}\n extensions={extensions}\n readOnly={readOnly}\n basicSetup={{\n lineNumbers,\n foldGutter: true,\n highlightActiveLineGutter: !readOnly,\n highlightActiveLine: !readOnly,\n tabSize: 2,\n }}\n height={height}\n className={`${className} no-track-pii-safe`}\n theme={themeExtension}\n />\n );\n}\n\nexport default CodeEditor;\n","\"use client\";\n\nimport { memo } from \"react\";\nimport YAML from \"yaml\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\nimport { CodeEditor } from \"../editor/CodeEditor\";\n\n/**\n * Props for generating a preset check template\n */\nexport interface GenerateCheckTemplateOptions {\n /** Check name */\n name: string;\n /** Check description */\n description: string;\n /** Check type (e.g., \"row_count_diff\", \"schema_diff\") */\n type: string;\n /** Check parameters */\n params: Record<string, unknown>;\n /** View options (optional) */\n viewOptions?: Record<string, unknown>;\n}\n\n/**\n * Generates a YAML template for a preset check configuration.\n * This can be used to export checks to recce.yml.\n *\n * @example\n * ```ts\n * const template = generateCheckTemplate({\n * name: \"Schema Check\",\n * description: \"Check for schema changes\",\n * type: \"schema_diff\",\n * params: { select: \"state:modified\" },\n * });\n * // Returns:\n * // checks:\n * // - name: Schema Check\n * // description: Check for schema changes\n * // type: schema_diff\n * // params:\n * // select: state:modified\n * ```\n */\nexport function generateCheckTemplate({\n name,\n description,\n type,\n params,\n viewOptions,\n}: GenerateCheckTemplateOptions): string {\n const check: Record<string, unknown> = { name, description, type, params };\n if (viewOptions) {\n check.view_options = viewOptions;\n }\n return YAML.stringify({\n checks: [check],\n });\n}\n\n/**\n * Props for the PresetCheckTemplateView component\n */\nexport interface PresetCheckTemplateViewProps {\n /** The YAML template string to display */\n yamlTemplate: string;\n /** Optional height for the editor */\n height?: string;\n}\n\n/**\n * PresetCheckTemplateView Component\n *\n * A presentation component for displaying a preset check YAML template\n * in a read-only code editor. Used in modals to show users how to\n * add checks to their recce.yml file.\n *\n * @example Basic usage\n * ```tsx\n * import { PresetCheckTemplateView, generateCheckTemplate } from '@datarecce/ui/primitives';\n *\n * function CheckTemplateDialog({ check }) {\n * const template = generateCheckTemplate({\n * name: check.name,\n * description: check.description,\n * type: check.type,\n * params: check.params,\n * });\n *\n * return (\n * <Dialog open>\n * <DialogContent>\n * <PresetCheckTemplateView yamlTemplate={template} />\n * </DialogContent>\n * </Dialog>\n * );\n * }\n * ```\n */\nfunction PresetCheckTemplateViewComponent({\n yamlTemplate,\n height = \"300px\",\n}: PresetCheckTemplateViewProps) {\n const isDark = useIsDark();\n return (\n <CodeEditor\n value={yamlTemplate}\n language=\"yaml\"\n readOnly={true}\n lineNumbers={false}\n wordWrap={true}\n fontSize={14}\n theme={isDark ? \"dark\" : \"light\"}\n height={height}\n />\n );\n}\n\nexport const PresetCheckTemplateView = memo(PresetCheckTemplateViewComponent);\nPresetCheckTemplateView.displayName = \"PresetCheckTemplateView\";\n","import axios, { type AxiosInstance } from \"axios\";\nimport { PUBLIC_API_URL } from \"../lib/const\";\nimport { useApiConfigOptional } from \"../providers\";\n\n/**\n * API configuration adapter for OSS and cloud hosts.\n *\n * @remarks\n * Fallback chain:\n * 1. RecceProvider (via useApiConfigOptional)\n * 2. Default config using PUBLIC_API_URL\n */\nexport interface ApiConfigContextType {\n /**\n * API endpoint prefix to replace `/api` in all requests.\n * For OSS: \"\" (empty string, uses default /api/* paths)\n * For Cloud: \"/api/v2/sessions/<session_id>\" (replaces /api with this)\n */\n apiPrefix: string;\n\n /**\n * Optional authentication token for API requests.\n * When provided, adds \"Authorization: Bearer <token>\" header.\n */\n authToken?: string;\n\n /**\n * Optional base URL override.\n * When provided, overrides the PUBLIC_API_URL for this context.\n */\n baseUrl?: string;\n /**\n * Pre-configured axios instance with interceptors for\n * API prefix replacement and auth token injection.\n */\n apiClient: AxiosInstance;\n}\n\n// Default config used when ApiConfigProvider is not present (OSS mode)\nconst defaultApiConfigContext: ApiConfigContextType = {\n apiPrefix: \"\",\n authToken: undefined,\n baseUrl: undefined,\n apiClient: axios.create({ baseURL: PUBLIC_API_URL }),\n};\n\n/**\n * Access the API configuration and configured axios client.\n *\n * @remarks\n * Priority order:\n * 1. RecceProvider (via useApiConfigOptional)\n * 2. Default config (for backward compatibility)\n */\nexport function useApiConfig(): ApiConfigContextType {\n // Call both hooks unconditionally (React hooks rules)\n const datarecceContext = useApiConfigOptional();\n\n // Priority: @datarecce/ui provider > defaults\n return datarecceContext ?? defaultApiConfigContext;\n}\n","/**\n * useAvatar - Hook for fetching user avatars with proper fallback handling.\n *\n * Solves the issue where email-only login users (non-GitHub) were showing\n * incorrect GitHub avatars because their Recce Cloud user_id was being\n * passed to the GitHub API.\n *\n * Logic:\n * 1. Fetch current user data to get login_type\n * 2. If actor's user_id matches current user AND login_type is \"github\":\n * - Fetch and return GitHub avatar URL\n * 3. Otherwise:\n * - Return null (component should show initials fallback)\n */\n\nimport { useQuery } from \"@tanstack/react-query\";\nimport { cacheKeys } from \"../api\";\nimport { fetchGitHubAvatar, fetchUser } from \"../lib/api/user\";\nimport { useApiConfig } from \"./useApiConfig\";\n\nexport interface UseAvatarOptions {\n /** The user ID from the event actor */\n userId: string | number | null | undefined;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseAvatarResult {\n /** The avatar URL if available, null otherwise */\n avatarUrl: string | null;\n /** Whether the avatar is loading */\n isLoading: boolean;\n /** Whether this user is a GitHub user */\n isGitHubUser: boolean;\n}\n\n/**\n * Hook to fetch a user's avatar with proper fallback handling.\n *\n * Only fetches GitHub avatars for users who authenticated via GitHub.\n * For email-only users, returns null so the component can show initials.\n *\n * @example\n * ```tsx\n * function UserAvatar({ userId, displayName }: Props) {\n * const { avatarUrl } = useAvatar({ userId });\n *\n * return (\n * <MuiAvatar src={avatarUrl || undefined}>\n * {displayName.charAt(0).toUpperCase()}\n * </MuiAvatar>\n * );\n * }\n * ```\n */\nexport function useAvatar({\n userId,\n enabled = true,\n}: UseAvatarOptions): UseAvatarResult {\n const { apiClient } = useApiConfig();\n const userIdString = userId?.toString();\n\n // Fetch current user to get login_type\n const { data: currentUser, isLoading: isUserLoading } = useQuery({\n queryKey: cacheKeys.user(),\n queryFn: () => fetchUser(apiClient),\n enabled: enabled && !!userIdString,\n retry: false,\n staleTime: 5 * 60 * 1000, // Cache for 5 minutes\n });\n\n // Determine if this is a GitHub user\n // Only fetch GitHub avatar if:\n // 1. The actor's user_id matches the current user's id\n // 2. The current user authenticated via GitHub\n const isCurrentUser = Boolean(\n currentUser && userIdString && currentUser.id === userIdString,\n );\n const isGitHubUser = isCurrentUser && currentUser?.login_type === \"github\";\n const shouldFetchAvatar = Boolean(enabled && isGitHubUser && userIdString);\n\n // Fetch GitHub avatar only for GitHub users\n const { data: avatarUrl, isLoading: isAvatarLoading } = useQuery({\n queryKey: [\"github-avatar\", userIdString],\n queryFn: () =>\n userIdString ? fetchGitHubAvatar(userIdString) : Promise.resolve(null),\n enabled: shouldFetchAvatar,\n retry: false,\n staleTime: 5 * 60 * 1000, // Cache for 5 minutes\n });\n\n return {\n avatarUrl: typeof avatarUrl === \"string\" ? avatarUrl : null,\n isLoading: isUserLoading || (shouldFetchAvatar && isAvatarLoading),\n isGitHubUser: Boolean(isGitHubUser),\n };\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport { memo, useCallback, useState } from \"react\";\nimport { useIsDark } from \"../../../hooks/useIsDark\";\n\n/**\n * Props for the CommentInput component\n */\nexport interface CommentInputProps {\n /** Callback when comment is submitted */\n onSubmit: (content: string) => void;\n /** Whether submission is in progress */\n isSubmitting?: boolean;\n /** Placeholder text for the input */\n placeholder?: string;\n /** Button text (defaults to \"Comment\") */\n submitLabel?: string;\n /** Submitting button text (defaults to \"Submitting...\") */\n submittingLabel?: string;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CommentInput Component\n *\n * A pure presentation component for adding comments to a timeline.\n * Supports Cmd+Enter (Mac) or Ctrl+Enter (Windows) keyboard shortcut to submit.\n *\n * @example Basic usage\n * ```tsx\n * import { CommentInput } from '@datarecce/ui/primitives';\n *\n * function CheckTimeline({ checkId }) {\n * const [isSubmitting, setIsSubmitting] = useState(false);\n *\n * const handleSubmit = async (content: string) => {\n * setIsSubmitting(true);\n * await addComment(checkId, content);\n * setIsSubmitting(false);\n * };\n *\n * return (\n * <CommentInput\n * onSubmit={handleSubmit}\n * isSubmitting={isSubmitting}\n * />\n * );\n * }\n * ```\n *\n * @example Custom labels\n * ```tsx\n * <CommentInput\n * onSubmit={handleSubmit}\n * placeholder=\"Leave feedback...\"\n * submitLabel=\"Send\"\n * submittingLabel=\"Sending...\"\n * />\n * ```\n */\nfunction CommentInputComponent({\n onSubmit,\n isSubmitting = false,\n placeholder = \"Add a comment...\",\n submitLabel = \"Comment\",\n submittingLabel = \"Submitting...\",\n className,\n}: CommentInputProps) {\n const isDark = useIsDark();\n const [content, setContent] = useState(\"\");\n\n const handleSubmit = useCallback(() => {\n const trimmed = content.trim();\n if (trimmed) {\n onSubmit(trimmed);\n setContent(\"\");\n }\n }, [content, onSubmit]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n // Submit on Cmd+Enter or Ctrl+Enter\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSubmit();\n }\n },\n [handleSubmit],\n );\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n setContent(e.target.value);\n },\n [],\n );\n\n return (\n <Box className={className}>\n <TextField\n value={content}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n size=\"small\"\n multiline\n fullWidth\n minRows={3}\n disabled={isSubmitting}\n sx={{\n \"& .MuiOutlinedInput-root\": {\n bgcolor: \"background.paper\",\n \"&:hover .MuiOutlinedInput-notchedOutline\": {\n borderColor: isDark ? \"grey.500\" : \"grey.400\",\n },\n \"&.Mui-focused .MuiOutlinedInput-notchedOutline\": {\n borderColor: \"iochmara.400\",\n boxShadow: \"0 0 0 1px #4299E1\",\n },\n },\n \"& .MuiOutlinedInput-notchedOutline\": {\n borderColor: isDark ? \"grey.600\" : undefined,\n },\n }}\n />\n <Stack direction=\"row\" justifyContent=\"flex-end\" sx={{ mt: 2 }}>\n <Button\n size=\"small\"\n color=\"iochmara\"\n variant=\"contained\"\n onClick={handleSubmit}\n disabled={!content.trim() || isSubmitting}\n >\n {isSubmitting ? submittingLabel : submitLabel}\n </Button>\n </Stack>\n </Box>\n );\n}\n\nexport const CommentInput = memo(CommentInputComponent);\nCommentInput.displayName = \"CommentInput\";\n","\"use client\";\n\nimport MuiAvatar from \"@mui/material/Avatar\";\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Popover from \"@mui/material/Popover\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport MuiTooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { type MouseEvent, memo, useCallback, useState } from \"react\";\nimport {\n PiBookmarkSimple,\n PiChatText,\n PiCheckCircle,\n PiCircle,\n PiNotePencil,\n PiPencilSimple,\n PiPlusCircle,\n PiTrashSimple,\n} from \"react-icons/pi\";\nimport { useIsDark } from \"../../../hooks/useIsDark\";\n\n/**\n * Actor information for timeline events\n */\nexport interface TimelineActor {\n /** User ID */\n user_id?: string | number;\n /** Full name of the actor */\n fullname?: string;\n /** Login/username of the actor */\n login?: string;\n /** Avatar URL (props-driven, consumer provides) */\n avatarUrl?: string;\n}\n\n/**\n * Event types supported by the timeline\n */\nexport type TimelineEventType =\n | \"check_created\"\n | \"comment\"\n | \"approval_change\"\n | \"description_change\"\n | \"name_change\"\n | \"preset_applied\";\n\n/**\n * Timeline event data structure\n */\nexport interface TimelineEventData {\n /** Unique event ID */\n id: string;\n /** Type of event */\n event_type: TimelineEventType;\n /** Actor who performed the event */\n actor: TimelineActor;\n /** Event creation timestamp (ISO string) */\n created_at: string;\n /** Event content (for comments) */\n content?: string;\n /** New value (for change events) */\n new_value?: string;\n /** Whether the event was edited */\n is_edited?: boolean;\n /** Whether the event was deleted */\n is_deleted?: boolean;\n}\n\n/**\n * Icon type mapping for events\n */\ntype EventIconType =\n | \"create\"\n | \"comment\"\n | \"approve\"\n | \"unapprove\"\n | \"edit\"\n | \"preset\";\n\n/**\n * Get the icon type for an event\n */\nfunction getEventIconType(event: TimelineEventData): EventIconType {\n switch (event.event_type) {\n case \"check_created\":\n return \"create\";\n case \"comment\":\n return \"comment\";\n case \"approval_change\":\n return event.new_value === \"true\" ? \"approve\" : \"unapprove\";\n case \"description_change\":\n case \"name_change\":\n return \"edit\";\n case \"preset_applied\":\n return \"preset\";\n default:\n return \"edit\";\n }\n}\n\n/**\n * Props for the TimelineEvent component\n */\nexport interface TimelineEventProps {\n /** Event data to render */\n event: TimelineEventData;\n /** Current user ID (to show edit/delete for own comments) */\n currentUserId?: string;\n /** Callback when editing a comment */\n onEdit?: (eventId: string, content: string) => Promise<void>;\n /** Callback when deleting a comment */\n onDelete?: (eventId: string) => Promise<void>;\n /** Optional markdown renderer component */\n markdownRenderer?: React.ComponentType<{ content: string }>;\n /** Optional CSS class name */\n className?: string;\n}\n\nfunction EventIcon({ event }: { event: TimelineEventData }) {\n const iconType = getEventIconType(event);\n\n const iconMap = {\n create: PiPlusCircle,\n comment: PiChatText,\n approve: PiCheckCircle,\n unapprove: PiCircle,\n edit: PiNotePencil,\n preset: PiBookmarkSimple,\n };\n\n const colorMap: Record<string, string> = {\n create: \"primary.main\",\n comment: \"grey.500\",\n approve: \"success.main\",\n unapprove: \"grey.400\",\n edit: \"warning.main\",\n preset: \"secondary.main\",\n };\n\n const IconComponent = iconMap[iconType];\n const color = colorMap[iconType];\n\n return <Box component={IconComponent} sx={{ color, fontSize: 16 }} />;\n}\n\nfunction UserAvatar({ actor }: { actor: TimelineActor }) {\n const displayName = actor.fullname || actor.login || \"User\";\n const initials = displayName.charAt(0).toUpperCase();\n\n return (\n <MuiAvatar\n src={actor.avatarUrl || undefined}\n sx={{ width: 24, height: 24, fontSize: \"0.75rem\" }}\n >\n {initials}\n </MuiAvatar>\n );\n}\n\nfunction StateChangeEventComponent({ event }: { event: TimelineEventData }) {\n const { actor } = event;\n const actorName = actor.fullname || actor.login || \"Someone\";\n const relativeTime = formatDistanceToNow(new Date(event.created_at), {\n addSuffix: true,\n });\n\n let message = \"\";\n switch (event.event_type) {\n case \"check_created\":\n message = \"created this check\";\n break;\n case \"approval_change\":\n message =\n event.new_value === \"true\"\n ? \"approved this check\"\n : \"unapproved this check\";\n break;\n case \"description_change\":\n message = \"updated the description\";\n break;\n case \"name_change\":\n message = \"renamed this check\";\n break;\n case \"preset_applied\":\n message = \"applied a preset\";\n break;\n default:\n message = \"made a change\";\n }\n\n return (\n <Box sx={{ display: \"flex\", gap: 1, alignItems: \"flex-start\", py: 1 }}>\n <Box sx={{ pt: \"2px\" }}>\n <EventIcon event={event} />\n </Box>\n <Box sx={{ flex: 1 }}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n flexWrap=\"wrap\"\n alignItems=\"center\"\n >\n <UserAvatar actor={actor} />\n <Typography variant=\"body2\" fontWeight=\"500\">\n {actorName}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {message}\n </Typography>\n <Typography variant=\"caption\" color=\"text.disabled\">\n {relativeTime}\n </Typography>\n </Stack>\n </Box>\n </Box>\n );\n}\n\nconst StateChangeEvent = memo(StateChangeEventComponent);\nStateChangeEvent.displayName = \"StateChangeEvent\";\n\nfunction CommentEventComponent({\n event,\n currentUserId,\n onEdit,\n onDelete,\n markdownRenderer: MarkdownRenderer,\n}: {\n event: TimelineEventData;\n currentUserId?: string;\n onEdit?: (eventId: string, content: string) => Promise<void>;\n onDelete?: (eventId: string) => Promise<void>;\n markdownRenderer?: React.ComponentType<{ content: string }>;\n}) {\n const isDark = useIsDark();\n const [isEditing, setIsEditing] = useState(false);\n const [editContent, setEditContent] = useState(event.content || \"\");\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [deleteAnchorEl, setDeleteAnchorEl] = useState<HTMLElement | null>(\n null,\n );\n const isDeletePopoverOpen = Boolean(deleteAnchorEl);\n\n const { actor } = event;\n const actorName = actor.fullname || actor.login || \"Someone\";\n const relativeTime = formatDistanceToNow(new Date(event.created_at), {\n addSuffix: true,\n });\n const isAuthor =\n currentUserId && String(actor.user_id) === String(currentUserId);\n\n const handleStartEdit = useCallback(() => {\n setEditContent(event.content || \"\");\n setIsEditing(true);\n }, [event.content]);\n\n const handleCancelEdit = useCallback(() => {\n setEditContent(event.content || \"\");\n setIsEditing(false);\n }, [event.content]);\n\n const handleSaveEdit = useCallback(async () => {\n const trimmed = editContent.trim();\n if (!trimmed || trimmed === event.content) {\n handleCancelEdit();\n return;\n }\n\n if (onEdit) {\n setIsSubmitting(true);\n try {\n await onEdit(event.id, trimmed);\n setIsEditing(false);\n } finally {\n setIsSubmitting(false);\n }\n }\n }, [editContent, event.content, event.id, onEdit, handleCancelEdit]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n handleCancelEdit();\n } else if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSaveEdit();\n }\n },\n [handleCancelEdit, handleSaveEdit],\n );\n\n const handleDeleteClick = useCallback(\n (clickEvent: MouseEvent<HTMLButtonElement>) => {\n setDeleteAnchorEl(clickEvent.currentTarget);\n },\n [],\n );\n\n const handleDeleteClose = useCallback(() => {\n setDeleteAnchorEl(null);\n }, []);\n\n const handleDelete = useCallback(async () => {\n if (onDelete) {\n setIsDeleting(true);\n try {\n await onDelete(event.id);\n handleDeleteClose();\n } finally {\n setIsDeleting(false);\n }\n }\n }, [onDelete, event.id, handleDeleteClose]);\n\n const handleEditChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n setEditContent(e.target.value);\n },\n [],\n );\n\n if (event.is_deleted) {\n return (\n <Box sx={{ display: \"flex\", gap: 1, alignItems: \"center\", py: 1 }}>\n <Box sx={{ pt: \"2px\", display: \"flex\", alignItems: \"center\" }}>\n <EventIcon event={event} />\n </Box>\n <Box sx={{ display: \"flex\", flex: 1, alignItems: \"center\" }}>\n <Typography variant=\"body2\" color=\"text.disabled\" fontStyle=\"italic\">\n Comment deleted\n </Typography>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box sx={{ display: \"flex\", gap: 1, alignItems: \"flex-start\", py: 1 }}>\n <Box sx={{ pt: \"2px\" }}>\n <EventIcon event={event} />\n </Box>\n <Box sx={{ flex: 1 }}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ mb: 0.5 }}\n flexWrap=\"wrap\"\n alignItems=\"center\"\n >\n <UserAvatar actor={actor} />\n <Typography variant=\"body2\" fontWeight=\"500\">\n {actorName}\n {isAuthor && (\n <Typography\n component=\"span\"\n variant=\"body2\"\n color=\"text.secondary\"\n >\n {\" \"}\n (Author)\n </Typography>\n )}\n </Typography>\n <Typography variant=\"caption\" color=\"text.disabled\">\n {relativeTime}\n </Typography>\n {event.is_edited && (\n <Typography variant=\"caption\" color=\"text.disabled\">\n (edited)\n </Typography>\n )}\n </Stack>\n\n {isEditing ? (\n // Edit mode\n <Box>\n <TextField\n value={editContent}\n onChange={handleEditChange}\n onKeyDown={handleKeyDown}\n size=\"small\"\n multiline\n minRows={3}\n fullWidth\n disabled={isSubmitting}\n autoFocus\n sx={{\n \"& .MuiOutlinedInput-root\": {\n bgcolor: \"background.paper\",\n \"&:focus-within\": {\n borderColor: \"primary.main\",\n },\n },\n }}\n />\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ mt: 1 }}\n justifyContent=\"flex-end\"\n >\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={handleCancelEdit}\n disabled={isSubmitting}\n >\n Cancel\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={handleSaveEdit}\n disabled={!editContent.trim() || isSubmitting}\n >\n {isSubmitting ? \"Saving...\" : \"Save\"}\n </Button>\n </Stack>\n </Box>\n ) : (\n // View mode\n <Box\n sx={{\n bgcolor: isDark ? \"grey.800\" : \"grey.50\",\n borderRadius: 1,\n p: 1,\n border: \"1px solid\",\n borderColor: isDark ? \"grey.700\" : \"grey.200\",\n position: \"relative\",\n \"&:hover .comment-actions\": {\n opacity: 1,\n },\n }}\n >\n {MarkdownRenderer ? (\n <MarkdownRenderer content={event.content || \"\"} />\n ) : (\n <Typography variant=\"body2\" sx={{ whiteSpace: \"pre-wrap\" }}>\n {event.content}\n </Typography>\n )}\n\n {/* Edit/Delete buttons - only visible to author on hover */}\n {isAuthor && (onEdit || onDelete) && (\n <Stack\n className=\"comment-actions\"\n direction=\"row\"\n spacing={0}\n sx={{\n position: \"absolute\",\n top: 4,\n right: 4,\n opacity: 0,\n transition: \"opacity 0.2s\",\n }}\n >\n {onEdit && (\n <MuiTooltip title=\"Edit comment\">\n <IconButton\n aria-label=\"Edit comment\"\n size=\"small\"\n onClick={handleStartEdit}\n >\n <PiPencilSimple />\n </IconButton>\n </MuiTooltip>\n )}\n {onDelete && (\n <>\n <MuiTooltip title=\"Delete comment\">\n <IconButton\n aria-label=\"Delete comment\"\n size=\"small\"\n color=\"error\"\n onClick={handleDeleteClick}\n >\n <PiTrashSimple />\n </IconButton>\n </MuiTooltip>\n <Popover\n open={isDeletePopoverOpen}\n anchorEl={deleteAnchorEl}\n onClose={handleDeleteClose}\n anchorOrigin={{\n vertical: \"bottom\",\n horizontal: \"center\",\n }}\n transformOrigin={{\n vertical: \"top\",\n horizontal: \"center\",\n }}\n >\n <Box sx={{ p: 2 }}>\n <Typography variant=\"body2\" sx={{ mb: 2 }}>\n Delete this comment?\n </Typography>\n <Stack\n direction=\"row\"\n spacing={1}\n justifyContent=\"flex-end\"\n >\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={handleDeleteClose}\n disabled={isDeleting}\n >\n Cancel\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"error\"\n onClick={handleDelete}\n disabled={isDeleting}\n >\n {isDeleting ? \"Deleting...\" : \"Delete\"}\n </Button>\n </Stack>\n </Box>\n </Popover>\n </>\n )}\n </Stack>\n )}\n </Box>\n )}\n </Box>\n </Box>\n );\n}\n\nconst CommentEvent = memo(CommentEventComponent);\nCommentEvent.displayName = \"CommentEvent\";\n\n/**\n * TimelineEvent Component\n *\n * A pure presentation component for rendering timeline events.\n * Supports different event types including comments with edit/delete functionality.\n *\n * @example Basic usage with state change events\n * ```tsx\n * import { TimelineEvent } from '@datarecce/ui/primitives';\n *\n * function CheckTimeline({ events }) {\n * return (\n * <div>\n * {events.map(event => (\n * <TimelineEvent key={event.id} event={event} />\n * ))}\n * </div>\n * );\n * }\n * ```\n *\n * @example With comment editing\n * ```tsx\n * <TimelineEvent\n * event={commentEvent}\n * currentUserId={currentUser.id}\n * onEdit={async (eventId, content) => {\n * await updateComment(eventId, content);\n * }}\n * onDelete={async (eventId) => {\n * await deleteComment(eventId);\n * }}\n * />\n * ```\n *\n * @example With custom markdown renderer\n * ```tsx\n * import { MarkdownContent } from '@/components/ui/markdown/MarkdownContent';\n *\n * <TimelineEvent\n * event={event}\n * markdownRenderer={({ content }) => (\n * <MarkdownContent content={content} fontSize=\"sm\" />\n * )}\n * />\n * ```\n */\nfunction TimelineEventComponent({\n event,\n currentUserId,\n onEdit,\n onDelete,\n markdownRenderer,\n className,\n}: TimelineEventProps) {\n if (event.event_type === \"comment\") {\n return (\n <Box className={className}>\n <CommentEvent\n event={event}\n currentUserId={currentUserId}\n onEdit={onEdit}\n onDelete={onDelete}\n markdownRenderer={markdownRenderer}\n />\n </Box>\n );\n }\n\n return (\n <Box className={className}>\n <StateChangeEvent event={event} />\n </Box>\n );\n}\n\nexport const TimelineEvent = memo(TimelineEventComponent);\nTimelineEvent.displayName = \"TimelineEvent\";\n","/**\n * Utility functions for check components\n */\n\n/**\n * Build a title string for a check with optional approval indicator\n *\n * @example\n * ```ts\n * const title = buildCheckTitle({ name: \"Schema check\", isChecked: true });\n * // Returns: \"✅ Schema check\"\n * ```\n */\nexport function buildCheckTitle({\n name,\n isChecked,\n}: {\n name: string;\n isChecked?: boolean;\n}): string {\n return `${isChecked ? \"✅ \" : \"\"}${name}`;\n}\n\n/**\n * Build a description string with fallback for empty descriptions\n *\n * @example\n * ```ts\n * const desc = buildCheckDescription({ description: \"\" });\n * // Returns: \"_(no description)_\"\n * ```\n */\nexport function buildCheckDescription({\n description,\n fallback = \"_(no description)_\",\n}: {\n description?: string | null;\n fallback?: string;\n}): string {\n return (description ?? \"\") || fallback;\n}\n\n/**\n * Check if a run result is missing or has an error\n *\n * Certain check types (schema_diff, lineage_diff) don't require results\n * to enable approval functionality.\n *\n * @example\n * ```ts\n * const disabled = isDisabledByNoResult({\n * type: \"row_count_diff\",\n * hasResult: false,\n * hasError: false,\n * });\n * // Returns: true\n *\n * const enabled = isDisabledByNoResult({\n * type: \"schema_diff\",\n * hasResult: false,\n * hasError: false,\n * });\n * // Returns: false (schema_diff doesn't require results)\n * ```\n */\nexport function isDisabledByNoResult({\n type,\n hasResult,\n hasError,\n}: {\n type: string;\n hasResult: boolean;\n hasError: boolean;\n}): boolean {\n // These types don't require results to enable approval\n if (type === \"schema_diff\" || type === \"lineage_diff\") {\n return false;\n }\n return !hasResult || hasError;\n}\n\n/**\n * Format SQL as a markdown code block\n *\n * @example\n * ```ts\n * const markdown = formatSqlAsMarkdown({ sql: \"SELECT * FROM users\" });\n * // Returns:\n * // **SQL**\n * // ```sql\n * // SELECT * FROM users\n * // ```\n * ```\n */\nexport function formatSqlAsMarkdown({\n sql,\n label = \"SQL\",\n}: {\n sql: string;\n label?: string;\n}): string {\n return `**${label}**\n\\`\\`\\`sql\n${sql}\n\\`\\`\\``;\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo } from \"react\";\nimport type { Run } from \"../../api\";\n\n/**\n * Run status types\n */\nexport type RunStatus = \"Running\" | \"Finished\" | \"Failed\" | \"Cancelled\";\n\n/**\n * Infer run status from Run object\n *\n * When status is not explicitly set, infers from result/error:\n * - Has result -> \"Finished\"\n * - Has error -> \"Failed\"\n * - Otherwise -> \"Finished\" (default)\n *\n * @param run - The run object to infer status from\n * @returns The inferred run status\n *\n * @example\n * ```tsx\n * const status = inferRunStatus(run);\n * // Returns run.status if set, otherwise infers from result/error\n * ```\n */\nexport function inferRunStatus(run: Run): RunStatus {\n if (run.status) {\n return run.status as RunStatus;\n }\n\n // Infer from result/error when status is missing\n if (run.result) {\n return \"Finished\";\n }\n if (run.error) {\n return \"Failed\";\n }\n\n // Default to finished\n return \"Finished\";\n}\n\n/**\n * Props for the RunStatusBadge component\n */\nexport interface RunStatusBadgeProps {\n /** Run status */\n status: RunStatus;\n /** Whether to show the spinner for running state */\n showSpinner?: boolean;\n /** Text size variant */\n size?: \"small\" | \"medium\";\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Get status display properties\n */\nfunction getStatusDisplay(status: RunStatus): { color: string; label: string } {\n switch (status) {\n case \"Running\":\n return { color: \"blue\", label: \"Running\" };\n case \"Finished\":\n return { color: \"green\", label: \"Finished\" };\n case \"Failed\":\n return { color: \"red\", label: \"Failed\" };\n case \"Cancelled\":\n return { color: \"grey\", label: \"Cancelled\" };\n default:\n return { color: \"green\", label: \"Finished\" };\n }\n}\n\n/**\n * RunStatusBadge Component\n *\n * A pure presentation component for displaying run status with color coding.\n *\n * @example Basic usage\n * ```tsx\n * import { RunStatusBadge } from '@datarecce/ui/primitives';\n *\n * function RunItem({ run }) {\n * return (\n * <div>\n * <span>{run.name}</span>\n * <RunStatusBadge status={run.status} />\n * </div>\n * );\n * }\n * ```\n *\n * @example With spinner\n * ```tsx\n * <RunStatusBadge\n * status=\"Running\"\n * showSpinner\n * />\n * ```\n */\nfunction RunStatusBadgeComponent({\n status,\n showSpinner = true,\n size = \"small\",\n className,\n}: RunStatusBadgeProps) {\n const { color, label } = getStatusDisplay(status);\n const isRunning = status === \"Running\";\n const fontSize = size === \"small\" ? \"0.75rem\" : \"0.875rem\";\n const spinnerSize = size === \"small\" ? 12 : 16;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 0.5,\n }}\n >\n {isRunning && showSpinner && (\n <CircularProgress size={spinnerSize} color=\"primary\" />\n )}\n <Typography\n component=\"span\"\n sx={{\n fontWeight: 500,\n fontSize,\n color: `${color}.500`,\n }}\n >\n {label}\n </Typography>\n </Box>\n );\n}\n\nexport const RunStatusBadge = memo(RunStatusBadgeComponent);\nRunStatusBadge.displayName = \"RunStatusBadge\";\n\n/**\n * Format date relative to today\n */\nexport function formatRunDate(date: Date | null): string | null {\n if (date == null) return null;\n\n const today = new Date();\n const yesterday = new Date();\n yesterday.setDate(today.getDate() - 1);\n\n if (today.toDateString() === date.toDateString()) {\n return \"Today\";\n } else if (yesterday.toDateString() === date.toDateString()) {\n return \"Yesterday\";\n } else {\n return date.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" });\n }\n}\n\n/**\n * Format date and time relative to today\n */\nexport function formatRunDateTime(date: Date | null): string | null {\n if (date == null) return null;\n\n const today = new Date();\n const yesterday = new Date();\n yesterday.setDate(today.getDate() - 1);\n const time = date.toLocaleTimeString(\"en-US\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n\n if (today.toDateString() === date.toDateString()) {\n return `Today, ${time}`;\n } else if (yesterday.toDateString() === date.toDateString()) {\n return `Yesterday, ${time}`;\n } else {\n const dateStr = date.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n });\n return `${dateStr}, ${time}`;\n }\n}\n\n/**\n * Props for RunStatusWithDate component\n */\nexport interface RunStatusWithDateProps {\n /** Run status */\n status: RunStatus;\n /** Run timestamp */\n runAt?: string | Date;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunStatusWithDate Component\n *\n * Displays status badge with formatted date/time.\n *\n * @example\n * ```tsx\n * <RunStatusWithDate\n * status=\"Finished\"\n * runAt={run.run_at}\n * />\n * ```\n */\nfunction RunStatusWithDateComponent({\n status,\n runAt,\n className,\n}: RunStatusWithDateProps) {\n const dateTime = runAt\n ? formatRunDateTime(typeof runAt === \"string\" ? new Date(runAt) : runAt)\n : null;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 0.5,\n fontSize: \"0.75rem\",\n color: \"text.secondary\",\n }}\n >\n <RunStatusBadge status={status} size=\"small\" />\n {dateTime && (\n <>\n <Typography component=\"span\" sx={{ fontSize: \"inherit\" }}>\n •\n </Typography>\n <Typography\n component=\"span\"\n sx={{\n fontSize: \"inherit\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {dateTime}\n </Typography>\n </>\n )}\n </Box>\n );\n}\n\nexport const RunStatusWithDate = memo(RunStatusWithDateComponent);\nRunStatusWithDate.displayName = \"RunStatusWithDate\";\n\n/**\n * Props for RunStatusAndDate component\n */\nexport interface RunStatusAndDateProps {\n /** The run object to display status and date for */\n run: Run;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunStatusAndDate Component\n *\n * Displays run status badge with formatted date/time.\n * Accepts a Run object and infers status when not explicitly set.\n *\n * @example\n * ```tsx\n * import { RunStatusAndDate } from '@datarecce/ui/components/run';\n *\n * function RunItem({ run }) {\n * return (\n * <div>\n * <span>{run.name}</span>\n * <RunStatusAndDate run={run} />\n * </div>\n * );\n * }\n * ```\n */\nfunction RunStatusAndDateComponent({ run, className }: RunStatusAndDateProps) {\n const status = inferRunStatus(run);\n\n return (\n <RunStatusWithDate\n status={status}\n runAt={run.run_at}\n className={className}\n />\n );\n}\n\nexport const RunStatusAndDate = memo(RunStatusAndDateComponent);\nRunStatusAndDate.displayName = \"RunStatusAndDate\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Stack from \"@mui/material/Stack\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode } from \"react\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\nimport {\n formatRunDate,\n type RunStatus,\n RunStatusWithDate,\n} from \"./RunStatusBadge\";\n\n/**\n * Run data for list display\n */\nexport interface RunListItemData {\n /** Unique run ID */\n id: string;\n /** Run name */\n name?: string;\n /** Run type identifier */\n type: string;\n /** Run status */\n status: RunStatus;\n /** Run timestamp */\n runAt?: string;\n /** Associated check ID (if linked to a check) */\n checkId?: string;\n}\n\n/**\n * Props for a single run list item\n */\nexport interface RunListItemProps {\n /** Run data */\n run: RunListItemData;\n /** Whether this item is selected */\n isSelected?: boolean;\n /** Icon for the run type */\n icon?: ReactNode;\n /** Callback when item is clicked */\n onClick?: (runId: string) => void;\n /** Callback when \"add to checklist\" is clicked */\n onAddToChecklist?: (runId: string) => void;\n /** Callback when \"go to check\" is clicked */\n onGoToCheck?: (checkId: string) => void;\n /** Icon for \"add to checklist\" action */\n addToChecklistIcon?: ReactNode;\n /** Icon for \"go to check\" action */\n goToCheckIcon?: ReactNode;\n /** Hide add to checklist action */\n hideAddToChecklist?: boolean;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunListItem Component\n *\n * A single item in a run list with selection state and actions.\n *\n * @example\n * ```tsx\n * <RunListItem\n * run={run}\n * isSelected={selectedRunId === run.id}\n * icon={<QueryIcon />}\n * onClick={(id) => setSelectedRunId(id)}\n * onAddToChecklist={(id) => addRunToChecklist(id)}\n * />\n * ```\n */\nfunction RunListItemComponent({\n run,\n isSelected = false,\n icon,\n onClick,\n onAddToChecklist,\n onGoToCheck,\n addToChecklistIcon,\n goToCheckIcon,\n hideAddToChecklist = false,\n className,\n}: RunListItemProps) {\n const isDark = useIsDark();\n const hasCheckLink = run.checkId != null;\n const showAddToChecklist =\n !hideAddToChecklist && !hasCheckLink && onAddToChecklist;\n\n return (\n <Box\n className={className}\n onClick={() => onClick?.(run.id)}\n sx={(theme) => ({\n display: \"flex\",\n flexDirection: \"column\",\n width: \"100%\",\n p: \"8px 16px\",\n cursor: \"pointer\",\n borderBottom: 1,\n borderColor: \"divider\",\n borderLeft: \"4px solid\",\n borderLeftColor: isSelected ? \"warning.main\" : \"transparent\",\n bgcolor: isSelected\n ? isDark\n ? \"warning.dark\"\n : \"warning.light\"\n : \"transparent\",\n \"&:hover\": {\n bgcolor: isSelected\n ? isDark\n ? \"warning.dark\"\n : \"warning.light\"\n : theme.palette.action.hover,\n },\n })}\n >\n {/* Name and actions row */}\n <Box sx={{ display: \"flex\", alignItems: \"center\", gap: 1.5 }}>\n {icon && (\n <Box sx={{ fontSize: 16, color: \"text.secondary\", flexShrink: 0 }}>\n {icon}\n </Box>\n )}\n <Typography\n sx={{\n flex: 1,\n fontSize: \"0.875rem\",\n fontWeight: 500,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n color: run.name ? \"text.primary\" : \"text.secondary\",\n }}\n >\n {run.name?.trim() || \"<no name>\"}\n </Typography>\n\n {/* Check link or add to checklist */}\n {hasCheckLink && onGoToCheck && (\n <Tooltip title=\"Go to Check\">\n <IconButton\n size=\"small\"\n onClick={(e) => {\n e.stopPropagation();\n if (run.checkId) onGoToCheck(run.checkId);\n }}\n sx={{ p: 0.5 }}\n >\n {goToCheckIcon || (\n <Box\n component=\"span\"\n sx={{ fontSize: 14, color: \"success.main\" }}\n >\n ✓\n </Box>\n )}\n </IconButton>\n </Tooltip>\n )}\n {showAddToChecklist && (\n <Tooltip title=\"Add to Checklist\">\n <IconButton\n size=\"small\"\n onClick={(e) => {\n e.stopPropagation();\n onAddToChecklist(run.id);\n }}\n sx={{ p: 0.5 }}\n >\n {addToChecklistIcon || (\n <Box component=\"span\" sx={{ fontSize: 14 }}>\n ○\n </Box>\n )}\n </IconButton>\n </Tooltip>\n )}\n </Box>\n\n {/* Status and date row */}\n <RunStatusWithDate status={run.status} runAt={run.runAt} />\n </Box>\n );\n}\n\nexport const RunListItem = memo(RunListItemComponent);\nRunListItem.displayName = \"RunListItem\";\n\n/**\n * Props for RunList component\n */\nexport interface RunListProps {\n /** List of runs to display */\n runs: RunListItemData[];\n /** Currently selected run ID */\n selectedId?: string | null;\n /** Whether the list is loading */\n isLoading?: boolean;\n /** Callback when a run is selected */\n onRunSelect?: (runId: string) => void;\n /** Callback when \"add to checklist\" is clicked */\n onAddToChecklist?: (runId: string) => void;\n /** Callback when \"go to check\" is clicked */\n onGoToCheck?: (checkId: string) => void;\n /** Function to get icon for run type */\n getRunIcon?: (runType: string) => ReactNode;\n /** Hide add to checklist action */\n hideAddToChecklist?: boolean;\n /** List title */\n title?: string;\n /** Header action buttons */\n headerActions?: ReactNode;\n /** Empty state message */\n emptyMessage?: string;\n /** Loading state message */\n loadingMessage?: string;\n /** Group runs by date */\n groupByDate?: boolean;\n /** Icon for \"add to checklist\" action - passed to all list items */\n addToChecklistIcon?: ReactNode;\n /** Icon for \"go to check\" action - passed to all list items */\n goToCheckIcon?: ReactNode;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Date segment divider\n */\ninterface DateSegmentProps {\n date: string;\n}\n\nconst DateSegment = memo(function DateSegment({ date }: DateSegmentProps) {\n return (\n <Box\n sx={{\n width: \"100%\",\n p: \"8px 16px\",\n borderBottom: 1,\n borderColor: \"divider\",\n color: \"text.secondary\",\n fontSize: \"0.75rem\",\n fontWeight: 500,\n bgcolor: \"action.hover\",\n }}\n >\n {date}\n </Box>\n );\n});\n\n/**\n * RunList Component\n *\n * A pure presentation component for displaying a list of runs with\n * selection, actions, and optional date grouping.\n *\n * @example Basic usage\n * ```tsx\n * import { RunList } from '@datarecce/ui/primitives';\n *\n * function HistoryPanel({ runs }) {\n * const [selectedId, setSelectedId] = useState(null);\n *\n * return (\n * <RunList\n * runs={runs}\n * selectedId={selectedId}\n * onRunSelect={setSelectedId}\n * title=\"History\"\n * groupByDate\n * />\n * );\n * }\n * ```\n *\n * @example With custom icons\n * ```tsx\n * <RunList\n * runs={runs}\n * selectedId={selectedId}\n * onRunSelect={setSelectedId}\n * getRunIcon={(type) => {\n * switch (type) {\n * case 'query': return <SqlIcon />;\n * case 'profile': return <ProfileIcon />;\n * default: return <DefaultIcon />;\n * }\n * }}\n * />\n * ```\n */\nfunction RunListComponent({\n runs,\n selectedId,\n isLoading = false,\n onRunSelect,\n onAddToChecklist,\n onGoToCheck,\n getRunIcon,\n hideAddToChecklist = false,\n title = \"Runs\",\n headerActions,\n emptyMessage = \"No runs\",\n loadingMessage = \"Loading...\",\n groupByDate = false,\n addToChecklistIcon,\n goToCheckIcon,\n className,\n}: RunListProps) {\n // Group runs by date if needed\n const renderRuns = () => {\n if (runs.length === 0) {\n return (\n <Box\n sx={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n height: \"100%\",\n color: \"text.secondary\",\n p: 4,\n }}\n >\n {emptyMessage}\n </Box>\n );\n }\n\n let previousDate: string | null = null;\n return runs.map((run) => {\n const currentDate = run.runAt ? formatRunDate(new Date(run.runAt)) : null;\n const showDateSegment =\n groupByDate && currentDate && previousDate !== currentDate;\n previousDate = currentDate;\n\n return (\n <Box key={run.id}>\n {showDateSegment && <DateSegment date={currentDate} />}\n <RunListItem\n run={run}\n isSelected={run.id === selectedId}\n icon={getRunIcon?.(run.type)}\n onClick={onRunSelect}\n onAddToChecklist={onAddToChecklist}\n onGoToCheck={onGoToCheck}\n hideAddToChecklist={hideAddToChecklist}\n addToChecklistIcon={addToChecklistIcon}\n goToCheckIcon={goToCheckIcon}\n />\n </Box>\n );\n });\n };\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n height: \"100%\",\n width: \"100%\",\n }}\n >\n {/* Header */}\n <Stack\n direction=\"row\"\n alignItems=\"center\"\n sx={{\n flex: \"0 0 auto\",\n px: 2,\n py: 1.5,\n borderBottom: 1,\n borderColor: \"divider\",\n }}\n >\n <Typography variant=\"h6\">{title}</Typography>\n <Box sx={{ flex: 1 }} />\n {headerActions}\n </Stack>\n\n {/* List content */}\n <Box\n sx={{\n flex: 1,\n overflow: \"auto\",\n }}\n >\n {isLoading ? (\n <Box\n sx={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n height: \"100%\",\n color: \"text.secondary\",\n }}\n >\n {loadingMessage}\n </Box>\n ) : (\n renderRuns()\n )}\n </Box>\n </Box>\n );\n}\n\nexport const RunList = memo(RunListComponent);\nRunList.displayName = \"RunList\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport LinearProgress from \"@mui/material/LinearProgress\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode } from \"react\";\nimport { type RunStatus, RunStatusBadge } from \"./RunStatusBadge\";\n\n/**\n * Progress variant types\n */\nexport type RunProgressVariant = \"spinner\" | \"linear\" | \"circular\";\n\n/**\n * Props for the RunProgress component\n */\nexport interface RunProgressProps {\n /** Run status */\n status: RunStatus;\n /** Progress percentage (0-100) for determinate progress */\n progress?: number;\n /** Progress message (e.g., \"Querying base...\") */\n message?: string;\n /** Error message when status is 'failed' */\n errorMessage?: string;\n /** Progress display variant */\n variant?: RunProgressVariant;\n /** Show status badge */\n showStatus?: boolean;\n /** Optional icon to display */\n icon?: ReactNode;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunProgress Component\n *\n * A pure presentation component for displaying run progress with\n * optional progress bar, message, and status.\n *\n * @example Basic spinner\n * ```tsx\n * import { RunProgress } from '@datarecce/ui/primitives';\n *\n * function RunningIndicator({ run }) {\n * return (\n * <RunProgress\n * status={run.status}\n * message=\"Executing query...\"\n * />\n * );\n * }\n * ```\n *\n * @example With progress bar\n * ```tsx\n * <RunProgress\n * status=\"Running\"\n * variant=\"linear\"\n * progress={65}\n * message=\"Processing records: 65,000 / 100,000\"\n * />\n * ```\n *\n * @example Error state\n * ```tsx\n * <RunProgress\n * status=\"Failed\"\n * errorMessage=\"Connection timeout after 30 seconds\"\n * />\n * ```\n */\nfunction RunProgressComponent({\n status,\n progress,\n message,\n errorMessage,\n variant = \"spinner\",\n showStatus = true,\n icon,\n className,\n}: RunProgressProps) {\n const isRunning = status === \"Running\";\n const isFailed = status === \"Failed\";\n const hasProgress = progress !== undefined && progress >= 0;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 2,\n p: 3,\n textAlign: \"center\",\n }}\n >\n {/* Icon or Progress indicator */}\n {icon ? (\n <Box sx={{ fontSize: 40, color: \"text.secondary\" }}>{icon}</Box>\n ) : (\n isRunning && (\n <>\n {variant === \"spinner\" && (\n <CircularProgress size={40} color=\"primary\" />\n )}\n {variant === \"circular\" && (\n <CircularProgress\n size={60}\n variant={hasProgress ? \"determinate\" : \"indeterminate\"}\n value={progress}\n color=\"primary\"\n />\n )}\n </>\n )\n )}\n\n {/* Linear progress bar */}\n {isRunning && variant === \"linear\" && (\n <Box sx={{ width: \"100%\", maxWidth: 300 }}>\n <LinearProgress\n variant={hasProgress ? \"determinate\" : \"indeterminate\"}\n value={progress}\n sx={{ height: 8, borderRadius: 4 }}\n />\n {hasProgress && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ mt: 0.5, display: \"block\" }}\n >\n {Math.round(progress)}%\n </Typography>\n )}\n </Box>\n )}\n\n {/* Status badge */}\n {showStatus && <RunStatusBadge status={status} size=\"medium\" />}\n\n {/* Message */}\n {message && !isFailed && (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {message}\n </Typography>\n )}\n\n {/* Error message */}\n {isFailed && errorMessage && (\n <Box\n sx={{\n p: 2,\n bgcolor: \"error.light\",\n borderRadius: 1,\n maxWidth: 400,\n }}\n >\n <Typography variant=\"body2\" color=\"error.contrastText\">\n {errorMessage}\n </Typography>\n </Box>\n )}\n </Box>\n );\n}\n\nexport const RunProgress = memo(RunProgressComponent);\nRunProgress.displayName = \"RunProgress\";\n\n/**\n * Props for RunProgressOverlay component\n */\nexport interface RunProgressOverlayProps extends RunProgressProps {\n /** Whether the overlay is visible */\n visible?: boolean;\n /** Background opacity (0-1) */\n opacity?: number;\n}\n\n/**\n * RunProgressOverlay Component\n *\n * A full-container overlay version of RunProgress.\n *\n * @example\n * ```tsx\n * <div style={{ position: 'relative', height: 400 }}>\n * <YourContent />\n * <RunProgressOverlay\n * visible={isLoading}\n * status=\"Running\"\n * message=\"Loading data...\"\n * />\n * </div>\n * ```\n */\nfunction RunProgressOverlayComponent({\n visible = true,\n opacity = 0.8,\n ...progressProps\n}: RunProgressOverlayProps) {\n if (!visible) return null;\n\n return (\n <Box\n sx={{\n position: \"absolute\",\n inset: 0,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: `rgba(255, 255, 255, ${opacity})`,\n zIndex: 10,\n \".dark &\": {\n bgcolor: `rgba(0, 0, 0, ${opacity})`,\n },\n }}\n >\n <RunProgress {...progressProps} />\n </Box>\n );\n}\n\nexport const RunProgressOverlay = memo(RunProgressOverlayComponent);\nRunProgressOverlay.displayName = \"RunProgressOverlay\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport { memo, type ReactNode } from \"react\";\nimport { PiWarning } from \"react-icons/pi\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\nimport { colors } from \"../../theme/colors\";\n\n/**\n * Common view options for diff-based result views.\n * Used to control what data is shown in diff results.\n */\nexport interface DiffViewOptions {\n /** When true, only show rows/columns that have changed */\n changed_only?: boolean;\n}\n\n/**\n * Props for the RunToolbar component\n */\nexport interface RunToolbarProps {\n /** Array of warning messages to display */\n warnings?: string[];\n /** Toolbar actions or other content */\n children?: ReactNode;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunToolbar Component\n *\n * A pure presentation component for displaying run toolbar with warnings.\n * Warnings are displayed with amber background when present.\n *\n * @example Basic usage with warnings\n * ```tsx\n * import { RunToolbar } from '@datarecce/ui/primitives';\n *\n * function RunPane({ run }) {\n * return (\n * <RunToolbar warnings={run.warnings}>\n * <Button onClick={handleExport}>Export</Button>\n * </RunToolbar>\n * );\n * }\n * ```\n *\n * @example Without warnings\n * ```tsx\n * <RunToolbar>\n * <Switch label=\"Changed only\" />\n * <Button onClick={handleCopy}>Copy</Button>\n * </RunToolbar>\n * ```\n */\nfunction RunToolbarComponent({\n warnings,\n children,\n className,\n}: RunToolbarProps) {\n const isDark = useIsDark();\n const hasWarnings = warnings && warnings.length > 0;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n borderBottom: \"1px solid\",\n borderColor: \"divider\",\n justifyContent: \"flex-end\",\n gap: \"5px\",\n alignItems: \"center\",\n px: \"10px\",\n bgcolor: hasWarnings\n ? isDark\n ? colors.amber[900]\n : colors.amber[100]\n : \"inherit\",\n color: hasWarnings\n ? isDark\n ? colors.amber[200]\n : colors.amber[800]\n : \"inherit\",\n }}\n >\n <Box\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"flex-start\",\n gap: 0,\n }}\n >\n {warnings?.map((warning, index) => (\n <Box key={warning}>\n <PiWarning\n color={isDark ? colors.amber[400] : colors.amber[600]}\n style={{ verticalAlign: \"middle\", marginRight: 4 }}\n />\n {warning}\n </Box>\n ))}\n </Box>\n <Box sx={{ flex: 1, minHeight: \"32px\" }} />\n {children}\n </Box>\n );\n}\n\nexport const RunToolbar = memo(RunToolbarComponent);\nRunToolbar.displayName = \"RunToolbar\";\n","\"use client\";\n\nimport {\n BarElement,\n CategoryScale,\n type ChartData,\n Chart as ChartJS,\n type ChartOptions,\n Legend,\n LinearScale,\n TimeSeriesScale,\n Title,\n Tooltip,\n} from \"chart.js\";\nimport { memo, useMemo } from \"react\";\nimport { Chart } from \"react-chartjs-2\";\n\n// Register Chart.js modules once\nChartJS.register(\n BarElement,\n TimeSeriesScale,\n LinearScale,\n CategoryScale,\n Title,\n Legend,\n Tooltip,\n);\n\n/**\n * Theme-aware colors for charts\n */\nexport interface ChartThemeColors {\n gridColor: string;\n textColor: string;\n borderColor: string;\n tooltipBackgroundColor: string;\n tooltipTextColor: string;\n /** Text color for labels drawn inside bars (must contrast with pastel bar fills) */\n barLabelColor: string;\n}\n\n/**\n * Bar colors for base/current comparison\n */\nexport interface ChartBarColors {\n current: string;\n base: string;\n currentWithAlpha: string;\n baseWithAlpha: string;\n}\n\n// Light mode colors\nconst CURRENT_BAR_COLOR = \"#63B3ED\";\nconst BASE_BAR_COLOR = \"#F6AD55\";\nconst CURRENT_BAR_COLOR_WITH_ALPHA = `${CURRENT_BAR_COLOR}A5`;\nconst BASE_BAR_COLOR_WITH_ALPHA = `${BASE_BAR_COLOR}A5`;\n\n// Dark mode colors\nconst CURRENT_BAR_COLOR_DARK = \"#90CDF4\";\nconst BASE_BAR_COLOR_DARK = \"#FBD38D\";\nconst CURRENT_BAR_COLOR_DARK_WITH_ALPHA = `${CURRENT_BAR_COLOR_DARK}A5`;\nconst BASE_BAR_COLOR_DARK_WITH_ALPHA = `${BASE_BAR_COLOR_DARK}A5`;\n\n/**\n * Get theme-aware colors for charts\n */\nexport function getChartThemeColors(isDark: boolean): ChartThemeColors {\n return {\n gridColor: isDark ? \"#4b5563\" : \"#d1d5db\",\n textColor: isDark ? \"#e5e7eb\" : \"#374151\",\n borderColor: isDark ? \"#6b7280\" : \"#9ca3af\",\n tooltipBackgroundColor: isDark ? \"#1f2937\" : \"#ffffff\",\n tooltipTextColor: isDark ? \"#e5e7eb\" : \"#111827\",\n barLabelColor: \"#1f2937\",\n };\n}\n\n/**\n * Get theme-aware bar colors\n */\nexport function getChartBarColors(isDark: boolean): ChartBarColors {\n return {\n current: isDark ? CURRENT_BAR_COLOR_DARK : CURRENT_BAR_COLOR,\n base: isDark ? BASE_BAR_COLOR_DARK : BASE_BAR_COLOR,\n currentWithAlpha: isDark\n ? CURRENT_BAR_COLOR_DARK_WITH_ALPHA\n : CURRENT_BAR_COLOR_WITH_ALPHA,\n baseWithAlpha: isDark\n ? BASE_BAR_COLOR_DARK_WITH_ALPHA\n : BASE_BAR_COLOR_WITH_ALPHA,\n };\n}\n\n/**\n * Histogram dataset for a single environment\n */\nexport interface HistogramDataset {\n /** Count values per bin */\n counts: number[];\n /** Optional dataset label */\n label?: string;\n}\n\n/**\n * Histogram data type\n */\nexport type HistogramDataType = \"numeric\" | \"datetime\" | \"string\";\n\n/**\n * Props for the HistogramChart component\n */\nexport interface HistogramChartProps {\n /** Chart title */\n title: string;\n /** Data type (numeric, datetime, or string) */\n dataType?: HistogramDataType;\n /** Total sample count */\n samples?: number;\n /** Minimum value (for datetime scale) */\n min?: string | number;\n /** Maximum value (for datetime scale) */\n max?: string | number;\n /** Bin edge values */\n binEdges: number[];\n /** Base environment dataset */\n baseData: HistogramDataset;\n /** Current environment dataset */\n currentData: HistogramDataset;\n /** Enable animation */\n animate?: boolean;\n /** Hide axis labels and ticks */\n hideAxis?: boolean;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Chart height in pixels */\n height?: number;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Format number as abbreviated (K, M, B, T)\n */\nfunction formatAbbreviatedNumber(input: number | string): string {\n if (typeof input !== \"number\") return String(input);\n\n const absValue = Math.abs(input);\n const trillion = 1e12;\n const billion = 1e9;\n const million = 1e6;\n const thousand = 1e3;\n\n if (absValue >= trillion) {\n return `${(input / trillion).toFixed(1)}T`;\n }\n if (absValue >= billion) {\n return `${(input / billion).toFixed(1)}B`;\n }\n if (absValue >= million) {\n return `${(input / million).toFixed(1)}M`;\n }\n if (absValue >= thousand) {\n return `${(input / thousand).toFixed(1)}K`;\n }\n if (absValue >= 1) {\n return input.toFixed(2);\n }\n if (absValue >= 0.01) {\n return input.toFixed(3);\n }\n return input.toExponential(2);\n}\n\n/**\n * Format bin range display\n */\nfunction formatBinRange(binEdges: number[], index: number): string {\n const start = binEdges[index];\n const end = binEdges[index + 1];\n return `${formatAbbreviatedNumber(start)} - ${formatAbbreviatedNumber(end)}`;\n}\n\n/**\n * Format percentage display\n */\nfunction formatPercentage(value: number): string {\n if (value > 0 && value <= 0.001) return \"<0.1%\";\n if (value < 1 && value >= 0.999) return \">99.9%\";\n return `${(value * 100).toFixed(1)}%`;\n}\n\n/**\n * HistogramChart Component\n *\n * A pure presentation component for displaying histogram charts comparing\n * base and current data distributions using Chart.js.\n *\n * @example Basic usage\n * ```tsx\n * import { HistogramChart } from '@datarecce/ui/primitives';\n *\n * function ProfilePanel({ histogramData }) {\n * return (\n * <HistogramChart\n * title=\"Age Distribution\"\n * dataType=\"numeric\"\n * binEdges={histogramData.binEdges}\n * baseData={{ counts: histogramData.baseCounts }}\n * currentData={{ counts: histogramData.currentCounts }}\n * samples={1000}\n * />\n * );\n * }\n * ```\n *\n * @example With datetime scale\n * ```tsx\n * <HistogramChart\n * title=\"Events Over Time\"\n * dataType=\"datetime\"\n * binEdges={timestamps}\n * baseData={{ counts: baseCounts }}\n * currentData={{ counts: currentCounts }}\n * min={startDate}\n * max={endDate}\n * />\n * ```\n */\nfunction HistogramChartComponent({\n title,\n dataType = \"numeric\",\n samples = 0,\n min = 0,\n max = 0,\n binEdges,\n baseData,\n currentData,\n animate = false,\n hideAxis = false,\n theme = \"light\",\n height = 300,\n className,\n}: HistogramChartProps) {\n const isDark = theme === \"dark\";\n const themeColors = getChartThemeColors(isDark);\n const barColors = getChartBarColors(isDark);\n const isDatetime = dataType === \"datetime\";\n\n // Build chart data\n const chartData = useMemo<ChartData<\"bar\">>(() => {\n const labels = binEdges\n .slice(0, -1)\n .map((_, i) => formatBinRange(binEdges, i));\n\n const buildDataset = (\n data: HistogramDataset,\n label: string,\n color: string,\n ) => {\n const counts = data.counts ?? [];\n const chartValues = isDatetime\n ? counts.map((v, i) => [binEdges[i], v] as [number, number])\n : counts;\n\n return {\n label,\n data: chartValues as number[],\n backgroundColor: color,\n borderColor: color,\n hoverBackgroundColor: color,\n borderWidth: 0,\n categoryPercentage: 1,\n barPercentage: 1,\n xAxisID: \"x\",\n };\n };\n\n return {\n labels,\n datasets: [\n buildDataset(\n currentData,\n currentData.label ?? \"Current\",\n barColors.currentWithAlpha,\n ),\n buildDataset(\n baseData,\n baseData.label ?? \"Base\",\n barColors.baseWithAlpha,\n ),\n ],\n };\n }, [binEdges, baseData, currentData, barColors, isDatetime]);\n\n // Build chart options\n const chartOptions = useMemo<ChartOptions<\"bar\">>(() => {\n const maxCount = Math.max(...currentData.counts, ...baseData.counts);\n\n const labels = binEdges\n .slice(0, -1)\n .map((_, i) => formatBinRange(binEdges, i));\n const dataTypeLabel = isDatetime\n ? \"Date Range\"\n : dataType === \"string\"\n ? \"Text Length\"\n : \"Value Range\";\n\n return {\n responsive: true,\n maintainAspectRatio: false,\n animation: animate ? undefined : false,\n plugins: {\n legend: {\n reverse: true,\n labels: {\n color: themeColors.textColor,\n },\n },\n title: {\n display: true,\n text: title,\n font: { size: 20 },\n color: themeColors.textColor,\n },\n tooltip: {\n mode: \"index\",\n intersect: false,\n backgroundColor: themeColors.tooltipBackgroundColor,\n titleColor: themeColors.tooltipTextColor,\n bodyColor: themeColors.tooltipTextColor,\n borderColor: themeColors.borderColor,\n borderWidth: 1,\n callbacks: {\n title([{ dataIndex }]) {\n const range = formatBinRange(binEdges, dataIndex);\n return `${dataTypeLabel}\\n${range}`;\n },\n label({ datasetIndex, dataIndex, dataset }) {\n const counts =\n datasetIndex === 0 ? currentData.counts : baseData.counts;\n const count = counts[dataIndex];\n const percent =\n samples > 0 ? formatPercentage(count / samples) : \"\";\n return `${dataset.label}: ${count}${percent ? ` (${percent})` : \"\"}`;\n },\n },\n },\n },\n scales: {\n x: isDatetime\n ? {\n display: !hideAxis,\n type: \"timeseries\",\n min,\n max,\n adapters: { date: {} },\n time: { minUnit: \"day\" },\n grid: { display: false },\n ticks: {\n minRotation: 30,\n maxRotation: 30,\n maxTicksLimit: 8,\n color: themeColors.textColor,\n },\n }\n : {\n display: !hideAxis,\n type: \"category\",\n grid: { display: false },\n ticks: {\n callback(_val, index) {\n return labels[index];\n },\n color: themeColors.textColor,\n },\n stacked: true,\n },\n y: {\n display: !hideAxis,\n type: \"linear\",\n max: maxCount,\n border: { dash: [2, 2], color: themeColors.borderColor },\n grid: { color: themeColors.gridColor },\n ticks: {\n maxTicksLimit: 8,\n color: themeColors.textColor,\n callback(val) {\n return formatAbbreviatedNumber(val as number);\n },\n },\n beginAtZero: true,\n },\n },\n };\n }, [\n title,\n dataType,\n isDatetime,\n samples,\n min,\n max,\n binEdges,\n baseData,\n currentData,\n hideAxis,\n animate,\n themeColors,\n ]);\n\n return (\n <div className={className} style={{ height }}>\n <Chart type=\"bar\" options={chartOptions} data={chartData} />\n </div>\n );\n}\n\nexport const HistogramChart = memo(HistogramChartComponent);\nHistogramChart.displayName = \"HistogramChart\";\n","\"use client\";\n\n/**\n * @file ScreenshotDataGrid.tsx\n * @description AG Grid wrapper component with screenshot support\n *\n * This component wraps AG Grid and provides:\n * - Theme switching (light/dark)\n * - Default grid configurations\n * - Screenshot capture support via ref\n * - Backward compatibility with react-data-grid API\n */\n\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport type {\n ColDef,\n ColGroupDef,\n GetRowIdParams,\n GridReadyEvent,\n} from \"ag-grid-community\";\nimport { AllCommunityModule, ModuleRegistry } from \"ag-grid-community\";\nimport { AgGridReact, type AgGridReactProps } from \"ag-grid-react\";\nimport React, {\n type CSSProperties,\n forwardRef,\n type Ref,\n useImperativeHandle,\n useMemo,\n useRef,\n} from \"react\";\n\nimport { useIsDark } from \"../../hooks\";\nimport \"./agGridStyles.css\";\nimport { dataGridThemeDark, dataGridThemeLight } from \"./agGridTheme\";\n\n// Register AG Grid modules once\nModuleRegistry.registerModules([AllCommunityModule]);\n\n/**\n * Handle type for accessing AG Grid API and DOM element (for screenshots)\n */\nexport interface DataGridHandle {\n api: GridReadyEvent[\"api\"] | null;\n /** DOM element for screenshot functionality */\n element: HTMLElement | null;\n}\n\n/**\n * Generic row type for data grids\n */\nexport interface DataGridRow {\n __rowKey?: string | number;\n [key: string]: unknown;\n}\n\n/**\n * Props for ScreenshotDataGrid component\n *\n * Supports both AG Grid style props (columnDefs/rowData) and\n * legacy react-data-grid style props (columns/rows) for backward compatibility\n */\nexport interface ScreenshotDataGridProps<TData = DataGridRow>\n extends Omit<AgGridReactProps<TData>, \"theme\" | \"rowClass\"> {\n /** Container style */\n style?: CSSProperties;\n /** Additional CSS class for container */\n className?: string;\n /** Empty state renderer (legacy) */\n renderers?: {\n noRowsFallback?: React.ReactNode;\n };\n /** Legacy: Column definitions (maps to columnDefs) */\n columns?: (ColDef<TData> | ColGroupDef<TData>)[];\n /** Legacy: Row data (maps to rowData) */\n rows?: TData[];\n /** Legacy: Default column options (maps to defaultColDef) */\n defaultColumnOptions?: ColDef<TData>;\n /** Optional CSS class to apply to rows (e.g., for PII tracking) */\n rowClassName?: string;\n /** Optional CSS class to apply to container (e.g., for PII tracking) */\n containerClassName?: string;\n}\n\n/**\n * Empty rows renderer component\n */\nexport interface EmptyRowsRendererProps {\n emptyMessage?: string;\n}\n\nexport function EmptyRowsRenderer({ emptyMessage }: EmptyRowsRendererProps) {\n return (\n <Box\n sx={{\n display: \"flex\",\n height: \"35px\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: \"grey.100\",\n textAlign: \"center\",\n gridColumn: \"1/-1\",\n }}\n >\n <Typography sx={{ fontWeight: 600 }}>\n {emptyMessage ?? \"No rows\"}\n </Typography>\n </Box>\n );\n}\n\n/**\n * AG Grid wrapper component with screenshot support\n *\n * @description Provides a themed AG Grid with default configurations:\n * - Automatic light/dark theme switching\n * - Row hover highlighting disabled by default\n * - Cell focus suppressed for cleaner UX\n *\n * Backward compatible with react-data-grid API:\n * - `columns` prop maps to `columnDefs`\n * - `rows` prop maps to `rowData`\n * - `defaultColumnOptions` maps to `defaultColDef`\n * - `renderers.noRowsFallback` maps to `noRowsOverlayComponent`\n *\n * @example\n * ```tsx\n * // AG Grid style\n * <ScreenshotDataGrid\n * columnDefs={columns}\n * rowData={rows}\n * style={{ height: '400px' }}\n * />\n *\n * // Legacy react-data-grid style\n * <ScreenshotDataGrid\n * columns={columns}\n * rows={rows}\n * renderers={{ noRowsFallback: <EmptyRowsRenderer /> }}\n * />\n * ```\n */\nfunction _ScreenshotDataGrid<TData = DataGridRow>(\n {\n style,\n className,\n columnDefs,\n rowData,\n columns,\n rows,\n getRowId,\n rowHeight = 32,\n headerHeight = 36,\n defaultColDef,\n defaultColumnOptions,\n renderers,\n rowClassName,\n containerClassName,\n ...props\n }: ScreenshotDataGridProps<TData>,\n ref: Ref<DataGridHandle>,\n) {\n // Container ref for screenshot functionality\n const containerRef = useRef<HTMLDivElement>(null);\n // AG Grid API ref\n const gridApiRef = useRef<GridReadyEvent[\"api\"] | null>(null);\n\n // Expose both API and DOM element through ref\n useImperativeHandle(\n ref,\n () => ({\n api: gridApiRef.current,\n element: containerRef.current,\n }),\n [],\n );\n\n // Use useIsDark for reliable dark mode detection with CSS Variables\n const isDark = useIsDark();\n\n // Select AG Grid theme based on dark mode\n const gridTheme = useMemo(\n () => (isDark ? dataGridThemeDark : dataGridThemeLight),\n [isDark],\n );\n\n // Support both new and legacy props\n const resolvedColumnDefs = columnDefs ?? columns;\n const resolvedRowData = rowData ?? rows;\n const resolvedDefaultColDef = defaultColDef ?? defaultColumnOptions;\n\n // Merge default column options\n const mergedDefaultColDef = useMemo<ColDef<TData>>(\n () => ({\n resizable: true,\n suppressMovable: true,\n ...resolvedDefaultColDef,\n }),\n [resolvedDefaultColDef],\n );\n\n // Custom overlay component when no rows\n const noRowsOverlayComponent = useMemo(() => {\n if (!renderers?.noRowsFallback) return undefined;\n return () => renderers.noRowsFallback;\n }, [renderers?.noRowsFallback]);\n\n // Generate row ID from __rowKey if available\n const resolvedGetRowId = useMemo(() => {\n if (getRowId) return getRowId;\n return (params: GetRowIdParams<TData>) => {\n const data = params.data as DataGridRow;\n if (data?.__rowKey !== undefined) {\n return String(data.__rowKey);\n }\n // Use rowIndex from the data or generate a random ID\n const index = (params.data as unknown as { rowIndex?: number })?.rowIndex;\n return String(index ?? Math.random());\n };\n }, [getRowId]);\n\n // Generate a key based on pinned columns to force AG Grid to remount\n const gridKey = useMemo(() => {\n if (!resolvedColumnDefs) return \"grid\";\n const pinnedFields = resolvedColumnDefs\n .filter(\n (col): col is ColDef<TData> => \"field\" in col && col.pinned === \"left\",\n )\n .map((col) => col.field)\n .sort()\n .join(\",\");\n return `grid-${pinnedFields}`;\n }, [resolvedColumnDefs]);\n\n // Combine class names\n const combinedClassName = [className, containerClassName]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <Box\n ref={containerRef}\n className={combinedClassName || undefined}\n sx={{\n // Use flex: 1 and minHeight: 0 for proper sizing in flex containers\n flex: 1,\n minHeight: 0,\n width: \"100%\",\n overflow: \"hidden\",\n \"& .ag-root-wrapper\": {\n border: \"none\",\n height: \"100%\",\n },\n \"& .ag-header\": {\n borderBottom: \"1px solid var(--ag-border-color)\",\n },\n \"& .ag-row\": {\n borderBottom: \"1px solid var(--ag-border-color)\",\n },\n \"& .ag-cell\": {\n borderRight: \"1px solid var(--ag-border-color)\",\n },\n \"& .ag-header-cell\": {\n borderRight: \"1px solid var(--ag-border-color)\",\n },\n // Diff cell styling - theme-aware colors\n \"& .diff-cell-added\": {\n backgroundColor: isDark ? \"#1a4d1a !important\" : \"#cefece !important\",\n color: \"var(--mui-palette-text-primary)\",\n },\n \"& .diff-cell-removed\": {\n backgroundColor: isDark ? \"#5c1f1f !important\" : \"#ffc5c5 !important\",\n color: \"var(--mui-palette-text-primary)\",\n },\n \"& .diff-cell-modified\": {\n backgroundColor: isDark ? \"#713F12 !important\" : \"#FEF3C7 !important\",\n color: \"var(--mui-palette-text-primary)\",\n },\n // Diff header styling\n \"& .diff-header-added\": {\n backgroundColor: \"#15803d !important\",\n color: \"white\",\n },\n \"& .diff-header-removed\": {\n backgroundColor: \"#f43f5e !important\",\n color: \"white\",\n },\n \"& .diff-header-modified\": {\n backgroundColor: \"#f59e0b !important\",\n color: \"white\",\n },\n // Index column styling\n \"& .index-column\": {\n color: \"var(--mui-palette-text-secondary)\",\n textAlign: \"right\",\n },\n // Frozen/pinned column styling\n \"& .ag-pinned-left-cols-container .ag-cell\": {\n backgroundColor: isDark ? \"#2d2d2d\" : \"#f5f5f5\",\n },\n }}\n >\n <AgGridReact<TData>\n key={gridKey}\n theme={gridTheme}\n columnDefs={resolvedColumnDefs}\n rowData={resolvedRowData}\n getRowId={resolvedGetRowId}\n rowHeight={rowHeight}\n headerHeight={headerHeight}\n defaultColDef={mergedDefaultColDef}\n suppressCellFocus={true}\n suppressRowHoverHighlight={false}\n animateRows={false}\n rowClass={rowClassName}\n noRowsOverlayComponent={noRowsOverlayComponent}\n onGridReady={(event) => {\n gridApiRef.current = event.api;\n }}\n {...props}\n />\n </Box>\n );\n}\n\nexport const ScreenshotDataGrid = forwardRef(_ScreenshotDataGrid) as <\n TData = DataGridRow,\n>(\n props: ScreenshotDataGridProps<TData> & { ref?: Ref<DataGridHandle> },\n) => React.ReactNode;\n\n// Re-export AG Grid types for convenience\nexport type { ColDef, ColGroupDef, GetRowIdParams, GridReadyEvent };\n\n// For backward compatibility\nexport type { DataGridHandle as RecceDataGridHandle };\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Divider from \"@mui/material/Divider\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport {\n BarElement,\n CategoryScale,\n type ChartData,\n Chart as ChartJS,\n type ChartOptions,\n Tooltip as ChartTooltip,\n Legend,\n LinearScale,\n type Plugin,\n type ScriptableScaleContext,\n} from \"chart.js\";\nimport { Fragment, memo, useMemo } from \"react\";\nimport { Bar } from \"react-chartjs-2\";\nimport { getChartBarColors, getChartThemeColors } from \"./HistogramChart\";\n\n/** Rendered bar geometry — Chart.js plugin API types this as Element but bar datasets provide these fields at runtime */\ninterface RenderedBarGeometry {\n x: number;\n y: number;\n base: number;\n}\n\n// Register Chart.js modules once\nChartJS.register(CategoryScale, BarElement, LinearScale, Legend, ChartTooltip);\n\n/**\n * Single Top-K value item\n */\nexport interface TopKItem {\n /** Value label */\n label: string;\n /** Count */\n count: number;\n /** Whether this is a special label (null, empty, others) */\n isSpecial?: boolean;\n}\n\n/**\n * Top-K dataset for a single environment\n * Compatible with TopKResult from @datarecce/ui/api\n */\nexport interface TopKDataset {\n /** Value labels (null or undefined treated as special) */\n values: (string | number | null | undefined)[];\n /** Counts per value */\n counts: number[];\n /** Total valid count */\n valids: number;\n}\n\n/**\n * Props for single horizontal bar\n */\nexport interface SingleBarChartProps {\n /** Count value */\n count: number;\n /** Total count for percentage calculation */\n total: number;\n /** Bar color */\n color?: string;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Chart height in pixels */\n height?: number;\n}\n\n/**\n * Props for the TopKBarChart component\n */\nexport interface TopKBarChartProps {\n /** Base environment dataset */\n baseData?: TopKDataset;\n /** Current environment dataset */\n currentData: TopKDataset;\n /** Maximum items to display (default 10) */\n maxItems?: number;\n /** Show comparison with base */\n showComparison?: boolean;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Chart title */\n title?: string;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Props for TopKSummaryList component\n */\nexport interface TopKSummaryListProps {\n /** Top-K dataset */\n data: TopKDataset;\n /** Maximum items to display */\n maxItems?: number;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Format number as abbreviated (K, M, B, T)\n */\nfunction formatAbbreviated(value: number): string {\n if (value >= 1e12) return `${(value / 1e12).toFixed(1)}T`;\n if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;\n if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;\n if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;\n return String(value);\n}\n\n/**\n * Format as percentage\n */\nfunction formatPercent(value: number): string {\n if (value > 0 && value <= 0.001) return \"<0.1%\";\n if (value < 1 && value >= 0.999) return \">99.9%\";\n return `${(value * 100).toFixed(1)}%`;\n}\n\n/**\n * Prepare summary list from dataset\n */\nfunction prepareSummaryList(\n dataset: TopKDataset,\n maxItems: number,\n): TopKItem[] {\n const endAt = Math.min(maxItems, dataset.counts.length);\n const displayedCounts = dataset.counts.slice(0, endAt);\n const displayedSum = displayedCounts.reduce((a, b) => a + b, 0);\n const remainingCount = dataset.valids - displayedSum;\n\n const items: TopKItem[] = displayedCounts.map((count, index) => {\n const value = dataset.values[index];\n let label: string;\n let isSpecial = false;\n\n if (value === null || value === undefined) {\n label = \"(null)\";\n isSpecial = true;\n } else if (typeof value === \"string\" && value.length === 0) {\n label = \"(empty)\";\n isSpecial = true;\n } else {\n label = String(value);\n }\n\n return { label, count, isSpecial };\n });\n\n // Add \"others\" if there's remaining count\n if (remainingCount > 0) {\n items.push({\n label: \"(others)\",\n count: remainingCount,\n isSpecial: true,\n });\n }\n\n return items;\n}\n\n/**\n * SingleBarChart Component\n *\n * A horizontal progress bar for displaying a single value.\n */\nfunction SingleBarChartComponent({\n count,\n total,\n color,\n theme = \"light\",\n height = 16,\n}: SingleBarChartProps) {\n const isDark = theme === \"dark\";\n const themeColors = getChartThemeColors(isDark);\n const barColors = getChartBarColors(isDark);\n const barColor = color ?? barColors.current;\n\n const chartData = useMemo<ChartData<\"bar\">>(\n () => ({\n labels: [\"\"],\n datasets: [\n {\n indexAxis: \"y\" as const,\n data: [count],\n backgroundColor: barColor,\n hoverBackgroundColor: barColor,\n borderWidth: 0,\n borderColor: barColor,\n barPercentage: 1,\n categoryPercentage: 0.6,\n },\n ],\n }),\n [count, barColor],\n );\n\n const chartOptions = useMemo<ChartOptions<\"bar\">>(\n () => ({\n responsive: true,\n maintainAspectRatio: false,\n indexAxis: \"y\" as const,\n scales: {\n x: {\n display: false,\n max: total,\n grid: { display: false },\n ticks: { color: themeColors.textColor },\n },\n y: {\n display: false,\n ticks: { color: themeColors.textColor },\n },\n },\n plugins: {\n tooltip: { enabled: false },\n },\n animation: false,\n }),\n [total, themeColors],\n );\n\n return (\n <div style={{ height, width: \"100%\" }}>\n <Bar data={chartData} options={chartOptions} />\n </div>\n );\n}\n\nexport const SingleBarChart = memo(SingleBarChartComponent);\nSingleBarChart.displayName = \"SingleBarChart\";\n\n/**\n * TopKSummaryList Component\n *\n * A list displaying top-k values with horizontal bar charts.\n *\n * @example Basic usage\n * ```tsx\n * import { TopKSummaryList } from '@datarecce/ui/primitives';\n *\n * function ValueDistribution({ topKData }) {\n * return (\n * <TopKSummaryList\n * data={topKData}\n * maxItems={10}\n * />\n * );\n * }\n * ```\n */\nfunction TopKSummaryListComponent({\n data,\n maxItems = 10,\n theme = \"light\",\n className,\n}: TopKSummaryListProps) {\n const isDark = theme === \"dark\";\n const barColors = getChartBarColors(isDark);\n const items = useMemo(\n () => prepareSummaryList(data, maxItems),\n [data, maxItems],\n );\n\n return (\n <Box className={className} sx={{ width: \"100%\" }}>\n {items.map((item) => (\n <Fragment key={item.label}>\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n width: \"100%\",\n \"&:hover\": { bgcolor: \"action.hover\" },\n px: 1.5,\n }}\n >\n <Tooltip title={item.label} placement=\"top-start\">\n <Typography\n sx={{\n width: \"14em\",\n fontSize: \"0.875rem\",\n color: item.isSpecial ? \"grey.400\" : \"inherit\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {item.label}\n </Typography>\n </Tooltip>\n <Box sx={{ display: \"flex\", height: \"2em\", width: \"10em\" }}>\n <SingleBarChart\n count={item.count}\n total={data.valids}\n color={barColors.current}\n theme={theme}\n />\n </Box>\n <Tooltip title={String(item.count)} placement=\"top-start\">\n <Typography\n sx={{\n ml: 2.5,\n mr: 1,\n fontSize: \"0.875rem\",\n width: \"4em\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {formatAbbreviated(item.count)}\n </Typography>\n </Tooltip>\n <Typography\n sx={{\n color: \"grey.400\",\n fontSize: \"0.875rem\",\n width: \"4em\",\n }}\n >\n {formatPercent(item.count / data.valids)}\n </Typography>\n </Box>\n <Divider />\n </Fragment>\n ))}\n </Box>\n );\n}\n\nexport const TopKSummaryList = memo(TopKSummaryListComponent);\nTopKSummaryList.displayName = \"TopKSummaryList\";\n\n/**\n * TopKBarChart Component\n *\n * A pure presentation component for displaying top-k value distributions\n * with optional base/current comparison.\n *\n * @example Basic usage\n * ```tsx\n * import { TopKBarChart } from '@datarecce/ui/primitives';\n *\n * function ValueDistribution({ topKData }) {\n * return (\n * <TopKBarChart\n * currentData={topKData}\n * maxItems={10}\n * />\n * );\n * }\n * ```\n *\n * @example With comparison\n * ```tsx\n * <TopKBarChart\n * baseData={baseTopK}\n * currentData={currentTopK}\n * showComparison\n * maxItems={10}\n * />\n * ```\n */\nfunction TopKBarChartComponent({\n baseData,\n currentData,\n maxItems = 10,\n showComparison = false,\n theme = \"light\",\n title,\n className,\n}: TopKBarChartProps) {\n const isDark = theme === \"dark\";\n const barColors = getChartBarColors(isDark);\n const themeColors = getChartThemeColors(isDark);\n\n const currentItems = useMemo(\n () => prepareSummaryList(currentData, maxItems),\n [currentData, maxItems],\n );\n\n const baseItems = useMemo(\n () => (baseData ? prepareSummaryList(baseData, maxItems) : []),\n [baseData, maxItems],\n );\n\n const showBase = showComparison && baseData && baseItems.length > 0;\n\n const currentTotal = currentData.valids || 1;\n const baseTotal = baseData?.valids || 1;\n\n // Build display items, filtering empty \"(others)\" rows\n const displayItems = useMemo(() => {\n return currentItems\n .map((current, index) => ({\n current,\n base: showBase ? (baseItems[index] ?? null) : null,\n }))\n .filter(\n ({ current, base }) =>\n !(\n current.label === \"(others)\" &&\n current.count === 0 &&\n (!base || base.count === 0)\n ),\n );\n }, [currentItems, baseItems, showBase]);\n\n // Normalize counts to proportions so bars represent distribution, not absolute volume.\n // This ensures identical distributions produce identical bar lengths regardless of scale.\n const chartData = useMemo<ChartData<\"bar\">>(() => {\n const labels = displayItems.map(({ current }) => current.label);\n\n const datasets: ChartData<\"bar\">[\"datasets\"] = [\n {\n label: \"Current\",\n data: displayItems.map(({ current }) => current.count / currentTotal),\n backgroundColor: barColors.current,\n hoverBackgroundColor: barColors.current,\n borderWidth: 0,\n borderRadius: 3,\n barPercentage: showBase ? 0.9 : 1,\n categoryPercentage: showBase ? 0.75 : 0.6,\n },\n ];\n\n if (showBase) {\n datasets.push({\n label: \"Base\",\n data: displayItems.map(({ base }) => (base?.count ?? 0) / baseTotal),\n backgroundColor: barColors.base,\n hoverBackgroundColor: barColors.base,\n borderWidth: 0,\n borderRadius: 3,\n barPercentage: 0.9,\n categoryPercentage: 0.75,\n });\n }\n\n return { labels, datasets };\n }, [displayItems, barColors, showBase, currentTotal, baseTotal]);\n\n const chartOptions = useMemo<ChartOptions<\"bar\">>(\n () => ({\n responsive: true,\n maintainAspectRatio: false,\n indexAxis: \"y\" as const,\n layout: { padding: { right: 80 } },\n scales: {\n x: {\n display: false,\n grid: { display: false },\n },\n y: {\n grid: { display: false },\n ticks: {\n padding: 8,\n color: (ctx: ScriptableScaleContext) => {\n const item = displayItems[ctx.index];\n return item?.current.isSpecial\n ? \"#9ca3af\"\n : themeColors.textColor;\n },\n },\n },\n },\n plugins: {\n legend: {\n display: !!showBase,\n position: \"top\" as const,\n align: \"center\" as const,\n reverse: true,\n labels: {\n boxWidth: 32,\n boxHeight: 12,\n borderRadius: 3,\n useBorderRadius: true,\n color: themeColors.textColor,\n },\n },\n tooltip: {\n mode: \"index\" as const,\n callbacks: {\n label: (context) => {\n const proportion = context.parsed.x ?? 0;\n const total =\n context.dataset.label === \"Base\" ? baseTotal : currentTotal;\n const count = Math.round(proportion * total);\n return `${context.dataset.label}: ${formatAbbreviated(count)} (${formatPercent(proportion)})`;\n },\n },\n },\n },\n animation: false,\n }),\n [displayItems, themeColors, showBase, baseTotal, currentTotal],\n );\n\n const secondaryTextColor = isDark ? \"#9ca3af\" : \"#6b7280\";\n\n const barLabelsPlugin = useMemo<Plugin<\"bar\">>(\n () => ({\n id: \"barLabels\",\n afterDatasetsDraw(chart) {\n const { ctx } = chart;\n ctx.save();\n ctx.font = \"11px system-ui, sans-serif\";\n ctx.textBaseline = \"middle\";\n const pad = 4;\n\n for (let dsIndex = 0; dsIndex < chart.data.datasets.length; dsIndex++) {\n const dataset = chart.data.datasets[dsIndex];\n const total = dataset.label === \"Base\" ? baseTotal : currentTotal;\n const meta = chart.getDatasetMeta(dsIndex);\n\n for (let i = 0; i < meta.data.length; i++) {\n const { x, y, base } = meta.data[\n i\n ] as unknown as RenderedBarGeometry;\n const proportion = (dataset.data[i] as number) ?? 0;\n if (proportion === 0) continue;\n const count = Math.round(proportion * total);\n\n const barWidth = x - base;\n const countText = formatAbbreviated(count);\n const pctText = formatPercent(proportion);\n const countWidth = ctx.measureText(countText).width;\n const fitsInside = countWidth + 2 * pad < barWidth;\n\n if (fitsInside) {\n ctx.fillStyle = themeColors.barLabelColor;\n ctx.textAlign = \"left\";\n ctx.fillText(countText, base + pad, y);\n ctx.fillStyle = secondaryTextColor;\n ctx.textAlign = \"left\";\n ctx.fillText(pctText, x + pad, y);\n } else {\n ctx.fillStyle = themeColors.textColor;\n ctx.textAlign = \"left\";\n ctx.fillText(countText, x + pad, y);\n ctx.fillStyle = secondaryTextColor;\n ctx.fillText(pctText, x + pad + countWidth + pad, y);\n }\n }\n }\n\n ctx.restore();\n },\n }),\n [baseTotal, currentTotal, themeColors, secondaryTextColor],\n );\n\n const chartHeight =\n displayItems.length * (showBase ? 46 : 32) + (showBase ? 30 : 0);\n\n return (\n <Box className={className} sx={{ width: \"100%\", px: 2, py: 2 }}>\n {title && (\n <Typography variant=\"subtitle1\" sx={{ fontWeight: 500, mb: 1 }}>\n {title}\n </Typography>\n )}\n <div style={{ height: Math.max(chartHeight, 50) }}>\n <Bar\n data={chartData}\n options={chartOptions}\n plugins={[barLabelsPlugin]}\n />\n </div>\n </Box>\n );\n}\n\nexport const TopKBarChart = memo(TopKBarChartComponent);\nTopKBarChart.displayName = \"TopKBarChart\";\n","\"use client\";\n\nimport { sql } from \"@codemirror/lang-sql\";\nimport { yaml } from \"@codemirror/lang-yaml\";\nimport { MergeView, unifiedMergeView } from \"@codemirror/merge\";\nimport { EditorState, type Extension } from \"@codemirror/state\";\nimport { EditorView, lineNumbers } from \"@codemirror/view\";\nimport Box from \"@mui/material/Box\";\nimport { memo, useEffect, useRef } from \"react\";\n\n/**\n * Supported languages for the diff editor\n */\nexport type DiffEditorLanguage = \"sql\" | \"yaml\" | \"text\";\n\n/**\n * Theme options for the diff editor\n */\nexport type DiffEditorTheme = \"light\" | \"dark\";\n\n/**\n * Props for the DiffEditor component\n */\nexport interface DiffEditorProps {\n /** Original (base) text content */\n original: string;\n /** Modified (current) text content */\n modified: string;\n /** Language for syntax highlighting */\n language?: DiffEditorLanguage;\n /** Whether editor is read-only */\n readOnly?: boolean;\n /** Show line numbers */\n lineNumbers?: boolean;\n /** Side-by-side view (true) or unified view (false) */\n sideBySide?: boolean;\n /** Editor height */\n height?: string;\n /** Theme mode */\n theme?: DiffEditorTheme;\n /** Callback when modified content changes */\n onModifiedChange?: (value: string) => void;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Get language extension for CodeMirror\n */\nfunction getLanguageExtension(language: DiffEditorLanguage): Extension | null {\n switch (language) {\n case \"sql\":\n return sql();\n case \"yaml\":\n return yaml();\n default:\n return null;\n }\n}\n\n/**\n * Get theme extensions for CodeMirror\n */\nfunction getThemeExtensions(isDark: boolean): Extension[] {\n const baseTheme = EditorView.theme(\n {\n \"&\": {\n backgroundColor: isDark ? \"#1e1e1e\" : \"#ffffff\",\n color: isDark ? \"#d4d4d4\" : \"#1f2937\",\n },\n \".cm-content\": {\n caretColor: isDark ? \"#d4d4d4\" : \"#1f2937\",\n fontFamily: \"'JetBrains Mono', 'Fira Code', monospace\",\n fontSize: \"13px\",\n },\n \".cm-gutters\": {\n backgroundColor: isDark ? \"#252526\" : \"#f5f5f5\",\n color: isDark ? \"#858585\" : \"#6b7280\",\n border: \"none\",\n },\n \".cm-activeLineGutter\": {\n backgroundColor: isDark ? \"#2a2d2e\" : \"#e5e7eb\",\n },\n \".cm-activeLine\": {\n backgroundColor: isDark ? \"#2a2d2e40\" : \"#f3f4f640\",\n },\n // Merge view specific styles\n \".cm-changedLine\": {\n backgroundColor: isDark ? \"#3d3d0050\" : \"#fff3c550\",\n },\n \".cm-changedText\": {\n backgroundColor: isDark ? \"#5c5c0080\" : \"#fef08a80\",\n },\n \".cm-deletedChunk\": {\n backgroundColor: isDark ? \"#5c1f1f50\" : \"#ffc5c550\",\n },\n \".cm-insertedChunk\": {\n backgroundColor: isDark ? \"#1a4d1a50\" : \"#cefece50\",\n },\n },\n { dark: isDark },\n );\n\n return [baseTheme];\n}\n\n/**\n * DiffEditor Component\n *\n * A pure presentation component for displaying text diffs using CodeMirror's\n * merge view. Supports side-by-side and unified diff views.\n *\n * @example Basic usage\n * ```tsx\n * import { DiffEditor } from '@datarecce/ui/primitives';\n *\n * function SqlDiffPanel({ baseSql, currentSql }) {\n * return (\n * <DiffEditor\n * original={baseSql}\n * modified={currentSql}\n * language=\"sql\"\n * sideBySide\n * />\n * );\n * }\n * ```\n *\n * @example Unified view with editing\n * ```tsx\n * const [modifiedSql, setModifiedSql] = useState(currentSql);\n *\n * <DiffEditor\n * original={baseSql}\n * modified={modifiedSql}\n * language=\"sql\"\n * sideBySide={false}\n * onModifiedChange={setModifiedSql}\n * />\n * ```\n */\nfunction DiffEditorComponent({\n original,\n modified,\n language = \"text\",\n readOnly = true,\n lineNumbers: showLineNumbers = true,\n sideBySide = true,\n height = \"400px\",\n theme = \"light\",\n onModifiedChange,\n className,\n}: DiffEditorProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const viewRef = useRef<MergeView | EditorView | null>(null);\n\n const isDark = theme === \"dark\";\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n // Clear previous view\n if (viewRef.current) {\n if (viewRef.current instanceof MergeView) {\n viewRef.current.destroy();\n } else {\n viewRef.current.destroy();\n }\n viewRef.current = null;\n }\n\n // Build extensions\n const extensions: Extension[] = [...getThemeExtensions(isDark)];\n\n if (showLineNumbers) {\n extensions.push(lineNumbers());\n }\n\n const langExt = getLanguageExtension(language);\n if (langExt) {\n extensions.push(langExt);\n }\n\n if (readOnly) {\n extensions.push(EditorState.readOnly.of(true));\n }\n\n // Create update listener if callback provided\n if (onModifiedChange && !readOnly) {\n extensions.push(\n EditorView.updateListener.of((update) => {\n if (update.docChanged) {\n onModifiedChange(update.state.doc.toString());\n }\n }),\n );\n }\n\n if (sideBySide) {\n // Side-by-side merge view\n // Note: revertControls is intentionally omitted - MergeView shows no\n // buttons when undefined (unlike unifiedMergeView which defaults to true)\n const mergeView = new MergeView({\n a: {\n doc: original,\n extensions: [...extensions],\n },\n b: {\n doc: modified,\n extensions: [...extensions],\n },\n parent: containerRef.current,\n orientation: \"a-b\",\n highlightChanges: true,\n gutter: true,\n collapseUnchanged: { margin: 3, minSize: 4 },\n });\n\n viewRef.current = mergeView;\n } else {\n // Unified diff view\n const unifiedExtensions = [\n ...extensions,\n unifiedMergeView({\n original,\n highlightChanges: true,\n gutter: true,\n // Disable accept/reject buttons - this is a read-only diff view\n mergeControls: false,\n collapseUnchanged: { margin: 3, minSize: 4 },\n }),\n ];\n\n const view = new EditorView({\n state: EditorState.create({\n doc: modified,\n extensions: unifiedExtensions,\n }),\n parent: containerRef.current,\n });\n\n viewRef.current = view;\n }\n\n return () => {\n if (viewRef.current) {\n if (viewRef.current instanceof MergeView) {\n viewRef.current.destroy();\n } else {\n viewRef.current.destroy();\n }\n viewRef.current = null;\n }\n };\n }, [\n original,\n modified,\n language,\n readOnly,\n showLineNumbers,\n sideBySide,\n isDark,\n onModifiedChange,\n ]);\n\n return (\n <Box\n ref={containerRef}\n className={className}\n sx={{\n height,\n width: \"100%\",\n overflow: \"auto\",\n border: \"1px solid\",\n borderColor: isDark ? \"grey.700\" : \"grey.300\",\n borderRadius: 1,\n \"& .cm-editor\": {\n height: \"100%\",\n },\n \"& .cm-scroller\": {\n overflow: \"auto\",\n },\n // Merge view layout\n \"& .cm-merge-view\": {\n height: \"100%\",\n },\n \"& .cm-merge-view > div\": {\n height: \"100%\",\n },\n }}\n />\n );\n}\n\nexport const DiffEditor = memo(DiffEditorComponent);\nDiffEditor.displayName = \"DiffEditor\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode } from \"react\";\n\n/**\n * Props for the EmptyState component\n */\nexport interface EmptyStateProps {\n /** Main title text */\n title: string;\n /** Description text */\n description?: string;\n /** Icon to display */\n icon?: ReactNode;\n /** Primary action button text */\n actionLabel?: string;\n /** Primary action callback */\n onAction?: () => void;\n /** Secondary action button text */\n secondaryActionLabel?: string;\n /** Secondary action callback */\n onSecondaryAction?: () => void;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Vertical padding */\n paddingY?: number;\n /** Optional CSS class */\n className?: string;\n /** Additional content below actions */\n children?: ReactNode;\n}\n\n/**\n * EmptyState Component\n *\n * A pure presentation component for displaying empty states\n * with optional icon, actions, and custom content.\n *\n * @example Basic usage\n * ```tsx\n * import { EmptyState } from '@datarecce/ui/primitives';\n *\n * function ChecksPanel({ checks }) {\n * if (checks.length === 0) {\n * return (\n * <EmptyState\n * title=\"No checks yet\"\n * description=\"Create your first check to get started\"\n * />\n * );\n * }\n * // ... render checks\n * }\n * ```\n *\n * @example With action button\n * ```tsx\n * <EmptyState\n * title=\"No results found\"\n * description=\"Try adjusting your search criteria\"\n * actionLabel=\"Clear Filters\"\n * onAction={() => clearFilters()}\n * />\n * ```\n *\n * @example With icon and multiple actions\n * ```tsx\n * <EmptyState\n * icon={<FolderIcon />}\n * title=\"No files\"\n * description=\"Upload files to get started\"\n * actionLabel=\"Upload File\"\n * onAction={handleUpload}\n * secondaryActionLabel=\"Learn More\"\n * onSecondaryAction={() => window.open(docsUrl)}\n * />\n * ```\n */\nfunction EmptyStateComponent({\n title,\n description,\n icon,\n actionLabel,\n onAction,\n secondaryActionLabel,\n onSecondaryAction,\n theme = \"light\",\n paddingY = 8,\n className,\n children,\n}: EmptyStateProps) {\n const isDark = theme === \"dark\";\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n textAlign: \"center\",\n py: paddingY,\n px: 4,\n height: \"100%\",\n minHeight: 200,\n }}\n >\n {/* Icon */}\n {icon && (\n <Box\n sx={{\n mb: 2,\n color: isDark ? \"grey.500\" : \"grey.400\",\n \"& svg\": {\n width: 48,\n height: 48,\n },\n }}\n >\n {icon}\n </Box>\n )}\n\n {/* Title */}\n <Typography\n variant=\"h6\"\n sx={{\n fontWeight: 500,\n color: isDark ? \"grey.300\" : \"grey.700\",\n mb: description ? 1 : 0,\n }}\n >\n {title}\n </Typography>\n\n {/* Description */}\n {description && (\n <Typography\n variant=\"body2\"\n sx={{\n color: isDark ? \"grey.400\" : \"grey.500\",\n maxWidth: 400,\n mb: actionLabel || secondaryActionLabel ? 3 : 0,\n }}\n >\n {description}\n </Typography>\n )}\n\n {/* Actions */}\n {(actionLabel || secondaryActionLabel) && (\n <Box sx={{ display: \"flex\", gap: 2, mt: 1 }}>\n {actionLabel && onAction && (\n <Button variant=\"contained\" onClick={onAction} size=\"small\">\n {actionLabel}\n </Button>\n )}\n {secondaryActionLabel && onSecondaryAction && (\n <Button variant=\"outlined\" onClick={onSecondaryAction} size=\"small\">\n {secondaryActionLabel}\n </Button>\n )}\n </Box>\n )}\n\n {/* Additional content */}\n {children && <Box sx={{ mt: 3 }}>{children}</Box>}\n </Box>\n );\n}\n\nexport const EmptyState = memo(EmptyStateComponent);\nEmptyState.displayName = \"EmptyState\";\n","\"use client\";\n\n/**\n * ExternalLinkConfirmDialog - Confirmation dialog for external links.\n *\n * Shows a warning when users click on links that navigate outside of Recce.\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Typography from \"@mui/material/Typography\";\nimport { useRef } from \"react\";\nimport { IoClose } from \"react-icons/io5\";\nimport { PiWarning } from \"react-icons/pi\";\n\n/**\n * Props for ExternalLinkConfirmDialog\n */\nexport interface ExternalLinkConfirmDialogProps {\n /** Whether the dialog is open */\n isOpen: boolean;\n /** The external URL the user is trying to navigate to */\n url: string;\n /** Callback when user confirms navigation */\n onConfirm: () => void;\n /** Callback when user cancels navigation */\n onCancel: () => void;\n}\n\n/**\n * Truncate a URL for display, keeping the domain visible\n */\nexport function truncateUrl(url: string, maxLength = 60): string {\n if (url.length <= maxLength) return url;\n\n try {\n const urlObj = new URL(url);\n const domain = urlObj.hostname;\n const path = urlObj.pathname + urlObj.search + urlObj.hash;\n\n // Always show the domain\n if (domain.length >= maxLength - 3) {\n return domain.substring(0, maxLength - 3) + \"...\";\n }\n\n // Calculate remaining space for path\n const remainingLength = maxLength - domain.length - 3;\n if (path.length > remainingLength) {\n return `${domain}${path.substring(0, remainingLength)}...`;\n }\n\n return url;\n } catch {\n // If URL parsing fails, just truncate normally\n return url.substring(0, maxLength - 3) + \"...\";\n }\n}\n\n/**\n * ExternalLinkConfirmDialog Component\n *\n * A dialog that asks users to confirm before navigating to external URLs.\n *\n * @example Basic usage\n * ```tsx\n * import { ExternalLinkConfirmDialog } from '@datarecce/ui/primitives';\n *\n * function MyComponent() {\n * const [isOpen, setIsOpen] = useState(false);\n * const [pendingUrl, setPendingUrl] = useState('');\n *\n * return (\n * <ExternalLinkConfirmDialog\n * isOpen={isOpen}\n * url={pendingUrl}\n * onConfirm={() => {\n * window.open(pendingUrl, '_blank');\n * setIsOpen(false);\n * }}\n * onCancel={() => setIsOpen(false)}\n * />\n * );\n * }\n * ```\n */\nexport function ExternalLinkConfirmDialog({\n isOpen,\n url,\n onConfirm,\n onCancel,\n}: ExternalLinkConfirmDialogProps) {\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n return (\n <MuiDialog\n open={isOpen}\n onClose={onCancel}\n maxWidth=\"sm\"\n fullWidth\n aria-labelledby=\"external-link-dialog-title\"\n >\n <DialogTitle\n id=\"external-link-dialog-title\"\n sx={{ display: \"flex\", alignItems: \"center\", gap: 1 }}\n >\n <Box component={PiWarning} sx={{ color: \"amber.500\", fontSize: 20 }} />\n External Link\n </DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={onCancel}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n\n <DialogContent>\n <Typography sx={{ mb: 1.5 }}>\n This link will take you to an external website outside of Recce. Are\n you sure you want to continue?\n </Typography>\n <Box\n sx={{\n bgcolor: \"grey.50\",\n p: 1,\n borderRadius: 1,\n border: \"1px solid\",\n borderColor: \"grey.200\",\n }}\n >\n <Box\n component=\"code\"\n sx={{\n fontSize: \"0.875rem\",\n wordBreak: \"break-all\",\n whiteSpace: \"pre-wrap\",\n bgcolor: \"transparent\",\n fontFamily: \"monospace\",\n }}\n >\n {truncateUrl(url, 100)}\n </Box>\n </Box>\n </DialogContent>\n\n <DialogActions sx={{ gap: 1 }}>\n <Button ref={cancelRef} variant=\"outlined\" onClick={onCancel}>\n Cancel\n </Button>\n <Button color=\"iochmara\" variant=\"contained\" onClick={onConfirm}>\n Open Link\n </Button>\n </DialogActions>\n </MuiDialog>\n );\n}\n","\"use client\";\n\n/**\n * MarkdownContent - Renders GitHub-flavored Markdown content.\n *\n * Features:\n * - GFM support (tables, task lists, strikethrough, autolinks)\n * - Syntax highlighting for code blocks\n * - External link confirmation dialog\n * - XSS-safe rendering (no dangerouslySetInnerHTML)\n */\n\nimport Box from \"@mui/material/Box\";\nimport Link from \"@mui/material/Link\";\nimport Typography from \"@mui/material/Typography\";\nimport React, { useState } from \"react\";\nimport Markdown, { Components } from \"react-markdown\";\nimport { Prism as SyntaxHighlighter } from \"react-syntax-highlighter\";\nimport { oneDark } from \"react-syntax-highlighter/dist/esm/styles/prism\";\nimport remarkGfm from \"remark-gfm\";\nimport { useIsDark } from \"../../hooks\";\nimport { ExternalLinkConfirmDialog } from \"./ExternalLinkConfirmDialog\";\n\n/**\n * Props for MarkdownContent component\n */\nexport interface MarkdownContentProps {\n /** The markdown content to render */\n content: string;\n /** Font size for the rendered content */\n fontSize?: string;\n /** Additional domains to treat as internal (not showing confirmation) */\n internalDomains?: string[];\n}\n\n/**\n * Check if a URL is external (not part of the Recce application)\n */\nfunction isExternalUrl(href: string, internalDomains: string[]): boolean {\n if (!href) return false;\n\n // Relative URLs are internal\n if (href.startsWith(\"/\") || href.startsWith(\"#\") || href.startsWith(\"?\")) {\n return false;\n }\n\n // Check for protocol-relative or absolute URLs\n try {\n const url = new URL(href, window.location.origin);\n\n // Check if the hostname matches any internal domain\n return !internalDomains.some(\n (domain) =>\n url.hostname === domain || url.hostname.endsWith(`.${domain}`),\n );\n } catch {\n // If URL parsing fails, treat as internal (likely a relative path)\n return false;\n }\n}\n\n/**\n * Custom link component that shows confirmation for external links\n */\nfunction MarkdownLink({\n href,\n children,\n internalDomains,\n}: {\n href?: string;\n children?: React.ReactNode;\n internalDomains: string[];\n}) {\n const [showConfirm, setShowConfirm] = useState(false);\n const [pendingUrl, setPendingUrl] = useState<string | null>(null);\n\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\n if (!href) return;\n\n if (isExternalUrl(href, internalDomains)) {\n e.preventDefault();\n setPendingUrl(href);\n setShowConfirm(true);\n }\n };\n\n const handleConfirm = () => {\n if (pendingUrl) {\n window.open(pendingUrl, \"_blank\", \"noopener,noreferrer\");\n }\n setShowConfirm(false);\n setPendingUrl(null);\n };\n\n const handleCancel = () => {\n setShowConfirm(false);\n setPendingUrl(null);\n };\n\n const isExternal = href ? isExternalUrl(href, internalDomains) : false;\n\n return (\n <>\n <Link\n href={href}\n onClick={handleClick}\n sx={{\n color: \"primary.main\",\n textDecoration: \"underline\",\n \"&:hover\": { color: \"iochmara.600\" },\n }}\n target=\"_blank\"\n rel={isExternal ? \"noopener noreferrer\" : undefined}\n >\n {children}\n {isExternal && \" ↗\"}\n </Link>\n <ExternalLinkConfirmDialog\n isOpen={showConfirm}\n url={pendingUrl || \"\"}\n onConfirm={handleConfirm}\n onCancel={handleCancel}\n />\n </>\n );\n}\n\n/**\n * Custom code block component with syntax highlighting\n */\nfunction CodeBlock({\n className,\n children,\n isDark = false,\n}: {\n className?: string;\n children?: React.ReactNode;\n node?: unknown;\n isDark?: boolean;\n}) {\n const match = /language-(\\w+)/.exec(className || \"\");\n const language = match ? match[1] : undefined;\n const codeString = String(children).replace(/\\n$/, \"\");\n\n // Check if this is an inline code block (no language, single line, no newlines)\n const isInline = !match && !String(children).includes(\"\\n\");\n\n if (isInline) {\n return (\n <Box\n component=\"code\"\n sx={{\n bgcolor: isDark ? \"grey.800\" : \"grey.100\",\n color: isDark ? \"grey.200\" : \"inherit\",\n px: 1,\n py: 0.5,\n borderRadius: 0.5,\n fontSize: \"0.9em\",\n fontFamily: \"monospace\",\n }}\n >\n {children}\n </Box>\n );\n }\n\n return (\n <Box\n sx={{ my: 2, borderRadius: 1, overflow: \"hidden\", fontSize: \"0.875rem\" }}\n >\n <SyntaxHighlighter\n style={oneDark}\n language={language}\n PreTag=\"div\"\n customStyle={{\n margin: 0,\n borderRadius: \"6px\",\n fontSize: \"0.85em\",\n }}\n >\n {codeString}\n </SyntaxHighlighter>\n </Box>\n );\n}\n\n/**\n * MarkdownContent Component\n *\n * A component for rendering GitHub-flavored Markdown with syntax highlighting\n * and external link confirmation.\n *\n * @example Basic usage\n * ```tsx\n * import { MarkdownContent } from '@datarecce/ui/primitives';\n *\n * function Description({ text }) {\n * return <MarkdownContent content={text} />;\n * }\n * ```\n *\n * @example With custom font size\n * ```tsx\n * <MarkdownContent content={markdown} fontSize=\"1rem\" />\n * ```\n *\n * @example With additional internal domains\n * ```tsx\n * <MarkdownContent\n * content={markdown}\n * internalDomains={['company.com', 'docs.company.com']}\n * />\n * ```\n */\nexport function MarkdownContent({\n content,\n fontSize = \"0.875rem\",\n internalDomains = [],\n}: MarkdownContentProps) {\n const isDark = useIsDark();\n\n // Build the list of internal domains\n const allInternalDomains = [\n window.location.hostname,\n \"reccehq.com\",\n \"datarecce.io\",\n \"localhost\",\n ...internalDomains,\n ];\n\n // Custom component renderers\n const components: Components = {\n // Links with external confirmation\n a: ({ href, children }) => (\n <MarkdownLink href={href} internalDomains={allInternalDomains}>\n {children}\n </MarkdownLink>\n ),\n\n // Code blocks with syntax highlighting\n code: (props) => <CodeBlock {...props} isDark={isDark} />,\n\n // Paragraphs\n p: ({ children }) => (\n <Typography\n component=\"p\"\n sx={{ fontSize, mb: 2, \"&:last-child\": { mb: 0 } }}\n >\n {children}\n </Typography>\n ),\n\n // Headers\n h1: ({ children }) => (\n <Typography\n sx={{ fontSize: \"1.25rem\", fontWeight: \"bold\", mb: 2, mt: 3 }}\n >\n {children}\n </Typography>\n ),\n h2: ({ children }) => (\n <Typography\n sx={{ fontSize: \"1.125rem\", fontWeight: \"bold\", mb: 2, mt: 3 }}\n >\n {children}\n </Typography>\n ),\n h3: ({ children }) => (\n <Typography sx={{ fontSize: \"1rem\", fontWeight: 600, mb: 2, mt: 2 }}>\n {children}\n </Typography>\n ),\n\n // Lists\n ul: ({ children }) => (\n <Box component=\"ul\" sx={{ pl: 4, mb: 2, listStyleType: \"disc\" }}>\n {children}\n </Box>\n ),\n ol: ({ children }) => (\n <Box component=\"ol\" sx={{ pl: 4, mb: 2, listStyleType: \"decimal\" }}>\n {children}\n </Box>\n ),\n li: ({ children }) => (\n <Box component=\"li\" sx={{ fontSize, mb: 1 }}>\n {children}\n </Box>\n ),\n\n // Blockquotes\n blockquote: ({ children }) => (\n <Box\n sx={{\n borderLeft: \"3px solid\",\n borderLeftColor: isDark ? \"grey.600\" : \"grey.300\",\n pl: 3,\n py: 1,\n my: 2,\n color: isDark ? \"grey.400\" : \"grey.600\",\n fontStyle: \"italic\",\n }}\n >\n {children}\n </Box>\n ),\n\n // Tables\n table: ({ children }) => (\n <Box sx={{ overflowX: \"auto\", my: 2 }}>\n <Box\n component=\"table\"\n sx={{\n width: \"100%\",\n fontSize,\n border: \"1px solid\",\n borderColor: isDark ? \"grey.700\" : \"grey.200\",\n borderRadius: 1,\n }}\n >\n {children}\n </Box>\n </Box>\n ),\n thead: ({ children }) => (\n <Box component=\"thead\" sx={{ bgcolor: isDark ? \"grey.800\" : \"grey.50\" }}>\n {children}\n </Box>\n ),\n tbody: ({ children }) => <Box component=\"tbody\">{children}</Box>,\n tr: ({ children }) => (\n <Box\n component=\"tr\"\n sx={{\n borderBottom: \"1px solid\",\n borderColor: isDark ? \"grey.700\" : \"grey.200\",\n }}\n >\n {children}\n </Box>\n ),\n th: ({ children }) => (\n <Box\n component=\"th\"\n sx={{ px: 2, py: 1, fontWeight: 600, textAlign: \"left\" }}\n >\n {children}\n </Box>\n ),\n td: ({ children }) => (\n <Box component=\"td\" sx={{ px: 2, py: 1 }}>\n {children}\n </Box>\n ),\n\n // Horizontal rule\n hr: () => (\n <Box\n component=\"hr\"\n sx={{ my: 3, borderColor: isDark ? \"grey.700\" : \"grey.200\" }}\n />\n ),\n\n // Strong/Bold\n strong: ({ children }) => (\n <Typography component=\"strong\" sx={{ fontWeight: 600 }}>\n {children}\n </Typography>\n ),\n\n // Emphasis/Italic\n em: ({ children }) => (\n <Typography component=\"em\" sx={{ fontStyle: \"italic\" }}>\n {children}\n </Typography>\n ),\n\n // Strikethrough\n del: ({ children }) => (\n <Typography\n component=\"del\"\n sx={{ textDecoration: \"line-through\", color: \"grey.500\" }}\n >\n {children}\n </Typography>\n ),\n };\n\n return (\n <Box className=\"markdown-content\">\n <Markdown remarkPlugins={[remarkGfm]} components={components}>\n {content}\n </Markdown>\n </Box>\n );\n}\n","\"use client\";\n\n/**\n * @file ScreenshotBox.tsx\n * @description A wrapper component for content that can be captured as a screenshot.\n *\n * This component provides a ref-forwardable container that can be used with\n * html-to-image or similar libraries to capture its contents as an image.\n */\n\nimport type { BoxProps } from \"@mui/material/Box\";\nimport Box from \"@mui/material/Box\";\nimport { forwardRef, type Ref } from \"react\";\n\nexport interface ScreenshotBoxProps extends BoxProps {\n /** Background color for the screenshot area */\n backgroundColor?: string;\n /** Block size (height in block direction) */\n blockSize?: string;\n /** Content to render inside the screenshot area */\n children?: React.ReactNode;\n}\n\n/**\n * A container component that can be captured as a screenshot.\n *\n * The component forwards its ref to the outer container, allowing parent\n * components to capture the element using html-to-image or similar libraries.\n *\n * @example\n * ```tsx\n * const ref = useRef<HTMLDivElement>(null);\n *\n * const captureScreenshot = async () => {\n * if (ref.current) {\n * const dataUrl = await toPng(ref.current);\n * // Use dataUrl...\n * }\n * };\n *\n * return (\n * <ScreenshotBox ref={ref} backgroundColor=\"white\">\n * <Chart data={data} />\n * </ScreenshotBox>\n * );\n * ```\n */\nexport const ScreenshotBox = forwardRef(function ScreenshotBox(\n {\n backgroundColor = \"white\",\n blockSize,\n children,\n ...restProps\n }: ScreenshotBoxProps,\n ref: Ref<HTMLDivElement>,\n) {\n return (\n <Box\n ref={ref}\n {...restProps}\n sx={{ overflowY: \"auto\", overflowX: \"hidden\", ...restProps.sx }}\n >\n <Box\n sx={{\n backgroundColor,\n height: \"100%\",\n blockSize,\n }}\n >\n {children}\n </Box>\n </Box>\n );\n});\n","\"use client\";\n\nimport \"./splitStyles.css\";\nimport Box from \"@mui/material/Box\";\nimport { type CSSProperties, memo, type ReactNode } from \"react\";\nimport Split from \"react-split\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\n\n/**\n * Split direction\n */\nexport type SplitDirection = \"horizontal\" | \"vertical\";\n\n/**\n * Props for the SplitPane component\n */\nexport interface SplitPaneProps {\n /** Child elements to split */\n children: ReactNode;\n /** Split direction */\n direction?: SplitDirection;\n /** Initial sizes as percentages (should sum to 100) */\n sizes?: number[];\n /** Minimum sizes in pixels */\n minSizes?: number | number[];\n /** Maximum sizes in pixels */\n maxSizes?: number | number[];\n /** Gutter (drag handle) size in pixels */\n gutterSize?: number;\n /** Snap to closed at this threshold (pixels) */\n snapOffset?: number;\n /** Allow dragging past minSize to collapse */\n dragInterval?: number;\n /** Callback when sizes change */\n onDragEnd?: (sizes: number[]) => void;\n /** Callback during drag */\n onDrag?: (sizes: number[]) => void;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Container style */\n style?: CSSProperties;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * SplitPane Component\n *\n * A pure presentation component for creating resizable split panes\n * using react-split. Supports horizontal and vertical layouts.\n *\n * @example Horizontal split\n * ```tsx\n * import { SplitPane } from '@datarecce/ui/primitives';\n *\n * function TwoColumnLayout() {\n * return (\n * <SplitPane direction=\"horizontal\" sizes={[30, 70]}>\n * <div>Left Panel</div>\n * <div>Right Panel</div>\n * </SplitPane>\n * );\n * }\n * ```\n *\n * @example Vertical split with min sizes\n * ```tsx\n * <SplitPane\n * direction=\"vertical\"\n * sizes={[50, 50]}\n * minSizes={[100, 100]}\n * >\n * <div>Top Panel</div>\n * <div>Bottom Panel</div>\n * </SplitPane>\n * ```\n *\n * @example Three-way split with callbacks\n * ```tsx\n * <SplitPane\n * direction=\"horizontal\"\n * sizes={[25, 50, 25]}\n * onDragEnd={(sizes) => saveSizes(sizes)}\n * >\n * <div>Navigation</div>\n * <div>Content</div>\n * <div>Details</div>\n * </SplitPane>\n * ```\n */\nfunction SplitPaneComponent({\n children,\n direction = \"horizontal\",\n sizes,\n minSizes = 0,\n maxSizes,\n gutterSize = 5,\n snapOffset = 30,\n dragInterval = 1,\n onDragEnd,\n onDrag,\n theme,\n style,\n className,\n}: SplitPaneProps) {\n const isDarkAuto = useIsDark();\n const isDark = theme ? theme === \"dark\" : isDarkAuto;\n\n const containerStyle: CSSProperties = {\n display: \"flex\",\n flexDirection: direction === \"horizontal\" ? \"row\" : \"column\",\n height: \"100%\",\n width: \"100%\",\n ...style,\n };\n\n return (\n <Box\n className={className}\n sx={{\n height: \"100%\",\n width: \"100%\",\n \"& .gutter\": {\n backgroundColor: \"divider\",\n backgroundRepeat: \"no-repeat\",\n backgroundPosition: \"50%\",\n transition: \"background-color 0.15s ease\",\n \"&:hover\": {\n backgroundColor: isDark ? \"grey.600\" : \"grey.300\",\n },\n },\n \"& .gutter.gutter-horizontal\": {\n cursor: \"col-resize\",\n },\n \"& .gutter.gutter-vertical\": {\n cursor: \"row-resize\",\n },\n }}\n >\n <Split\n style={containerStyle}\n direction={direction}\n sizes={sizes}\n minSize={minSizes}\n maxSize={maxSizes}\n gutterSize={gutterSize}\n snapOffset={snapOffset}\n dragInterval={dragInterval}\n onDragEnd={onDragEnd}\n onDrag={onDrag}\n >\n {children}\n </Split>\n </Box>\n );\n}\n\nexport const SplitPane = memo(SplitPaneComponent);\nSplitPane.displayName = \"SplitPane\";\n","\"use client\";\n\nimport type { SplitProps as ReactSplitProps } from \"react-split\";\nimport { SplitPane } from \"./SplitPane\";\n\n/**\n * Props for HSplit and VSplit components\n *\n * These components provide backward-compatible wrappers around SplitPane,\n * accepting the same props as react-split's SplitProps.\n */\nexport type SplitProps = ReactSplitProps;\n\n/**\n * Horizontal Split Component\n *\n * A convenience wrapper around SplitPane that creates a horizontal (left-right) split.\n * Maintains backward compatibility with react-split's SplitProps interface.\n *\n * @example Basic horizontal split\n * ```tsx\n * import { HSplit } from '@datarecce/ui';\n *\n * function TwoColumnLayout() {\n * return (\n * <HSplit sizes={[30, 70]} minSize={100}>\n * <div>Left Panel</div>\n * <div>Right Panel</div>\n * </HSplit>\n * );\n * }\n * ```\n *\n * @example With custom styling\n * ```tsx\n * <HSplit\n * sizes={[20, 80]}\n * minSize={50}\n * style={{ height: \"100%\" }}\n * >\n * <nav>Navigation</nav>\n * <main>Content</main>\n * </HSplit>\n * ```\n */\nexport function HSplit(props: SplitProps) {\n const {\n style,\n children,\n gutterSize = 5,\n minSize,\n maxSize,\n sizes,\n snapOffset,\n dragInterval,\n onDragEnd,\n onDrag,\n className,\n } = props;\n\n return (\n <SplitPane\n direction=\"horizontal\"\n gutterSize={gutterSize}\n minSizes={minSize}\n maxSizes={maxSize}\n sizes={sizes}\n snapOffset={typeof snapOffset === \"number\" ? snapOffset : undefined}\n dragInterval={dragInterval}\n onDragEnd={onDragEnd}\n onDrag={onDrag}\n style={style}\n className={className}\n >\n {children}\n </SplitPane>\n );\n}\n\n/**\n * Vertical Split Component\n *\n * A convenience wrapper around SplitPane that creates a vertical (top-bottom) split.\n * Maintains backward compatibility with react-split's SplitProps interface.\n *\n * @example Basic vertical split\n * ```tsx\n * import { VSplit } from '@datarecce/ui';\n *\n * function TwoRowLayout() {\n * return (\n * <VSplit sizes={[60, 40]} minSize={100}>\n * <div>Top Panel</div>\n * <div>Bottom Panel</div>\n * </VSplit>\n * );\n * }\n * ```\n *\n * @example Nested splits\n * ```tsx\n * <HSplit sizes={[30, 70]}>\n * <div>Sidebar</div>\n * <VSplit sizes={[70, 30]}>\n * <div>Main Content</div>\n * <div>Footer</div>\n * </VSplit>\n * </HSplit>\n * ```\n */\nexport function VSplit(props: SplitProps) {\n const {\n style,\n children,\n gutterSize = 5,\n minSize,\n maxSize,\n sizes,\n snapOffset,\n dragInterval,\n onDragEnd,\n onDrag,\n className,\n } = props;\n\n return (\n <SplitPane\n direction=\"vertical\"\n gutterSize={gutterSize}\n minSizes={minSize}\n maxSizes={maxSize}\n sizes={sizes}\n snapOffset={typeof snapOffset === \"number\" ? snapOffset : undefined}\n dragInterval={dragInterval}\n onDragEnd={onDragEnd}\n onDrag={onDrag}\n style={style}\n className={className}\n >\n {children}\n </SplitPane>\n );\n}\n","\"use client\";\n\nimport Alert from \"@mui/material/Alert\";\nimport Box from \"@mui/material/Box\";\nimport { amber } from \"@mui/material/colors\";\nimport { forwardRef, type ReactNode, type Ref, useMemo } from \"react\";\nimport { PiWarning } from \"react-icons/pi\";\n\nimport { useIsDark } from \"../../hooks\";\nimport {\n type DataGridHandle,\n EmptyRowsRenderer,\n ScreenshotDataGrid,\n} from \"../data/ScreenshotDataGrid\";\nimport { ScreenshotBox } from \"../ui/ScreenshotBox\";\nimport type {\n CreatedResultViewProps,\n ResultViewConfig,\n ResultViewRef,\n WarningStyle,\n} from \"./types\";\n\n/**\n * Renders a single warning with amber styling (icon + text).\n */\nfunction AmberWarning({\n warning,\n isDark,\n}: {\n warning: string;\n isDark: boolean;\n}) {\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 0.5,\n fontSize: \"0.75rem\",\n }}\n >\n <PiWarning color={isDark ? amber[400] : amber[600]} />\n <Box>{warning}</Box>\n </Box>\n );\n}\n\n/**\n * Toolbar area component for ResultView.\n * Renders warnings on the left, spacer, and toolbar controls on the right.\n */\nfunction ToolbarArea({\n toolbar,\n warnings,\n warningStyle = \"alert\",\n isDark,\n}: {\n toolbar?: ReactNode;\n warnings?: string[];\n warningStyle?: WarningStyle;\n isDark: boolean;\n}) {\n if (!toolbar && (!warnings || warnings.length === 0)) {\n return null;\n }\n\n // Determine background color based on warning style\n const bgColor =\n warningStyle === \"amber\" && warnings && warnings.length > 0\n ? isDark\n ? amber[900]\n : amber[100]\n : isDark\n ? \"grey.900\"\n : \"grey.50\";\n\n // Determine text color for amber style\n const textColor =\n warningStyle === \"amber\" && warnings && warnings.length > 0\n ? isDark\n ? amber[200]\n : amber[800]\n : undefined;\n\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 1,\n px: 1,\n py: 0.5,\n borderBottom: 1,\n borderColor: \"divider\",\n bgcolor: bgColor,\n color: textColor,\n }}\n >\n {warningStyle === \"amber\"\n ? warnings?.map((warning) => (\n <AmberWarning key={warning} warning={warning} isDark={isDark} />\n ))\n : warnings?.map((warning) => (\n <Alert\n key={warning}\n severity=\"warning\"\n sx={{ py: 0, fontSize: \"0.75rem\" }}\n >\n {warning}\n </Alert>\n ))}\n <Box sx={{ flex: 1 }} />\n {toolbar}\n </Box>\n );\n}\n\n/**\n * Factory function to create type-safe ResultView components.\n *\n * @remarks\n * Reduces boilerplate by handling:\n * - Type guard validation with consistent error messages\n * - forwardRef setup for screenshot capture\n * - Dark/light theme handling\n * - Empty state rendering\n *\n * @typeParam TRun - Run payload type validated by the type guard.\n * @typeParam TViewOptions - Optional view options shape used by the view.\n * @typeParam TRef - Ref type exposed by the view (defaults to DataGridHandle).\n *\n * @example\n * ```tsx\n * export const RowCountResultView = createResultView({\n * displayName: \"RowCountResultView\",\n * typeGuard: isRowCountRun,\n * expectedRunType: \"row_count\",\n * screenshotWrapper: \"grid\",\n * transformData: (run) => ({\n * columns: toRowCountGrid(run).columns,\n * rows: toRowCountGrid(run).rows,\n * }),\n * });\n * ```\n */\nexport function createResultView<\n TRun,\n TViewOptions = unknown,\n TRef extends ResultViewRef = DataGridHandle,\n>(config: ResultViewConfig<TRun, TViewOptions>) {\n const {\n displayName,\n typeGuard,\n expectedRunType,\n screenshotWrapper,\n transformData,\n emptyState = \"No data\",\n conditionalEmptyState,\n } = config;\n\n function ResultViewInner(\n {\n run,\n viewOptions,\n onViewOptionsChanged,\n onAddToChecklist,\n }: CreatedResultViewProps<TViewOptions>,\n ref: Ref<TRef>,\n ) {\n const isDark = useIsDark();\n\n // Type guard validation\n if (!typeGuard(run)) {\n throw new Error(`Run type must be ${expectedRunType}`);\n }\n\n // Transform data - memoized for performance (must be called before conditional returns)\n const data = useMemo(\n () =>\n transformData(run, {\n viewOptions,\n onViewOptionsChanged,\n onAddToChecklist,\n }),\n [run, viewOptions, onViewOptionsChanged, onAddToChecklist],\n );\n\n // Check conditional empty state\n const conditionalEmpty = conditionalEmptyState?.(run, viewOptions);\n if (conditionalEmpty !== null && conditionalEmpty !== undefined) {\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n height: \"100%\",\n }}\n >\n {conditionalEmpty}\n </Box>\n );\n }\n\n // Return null case (component renders nothing)\n if (data?.renderNull) {\n return null;\n }\n\n // Empty state\n if (!data || data.isEmpty) {\n const hasToolbar =\n data?.toolbar || (data?.warnings && data.warnings.length > 0);\n\n // Empty state WITH toolbar (for patterns like ValueDiffDetailResultView \"No change\")\n if (hasToolbar) {\n return (\n <Box\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n height: \"100%\",\n }}\n >\n <ToolbarArea\n toolbar={data?.toolbar}\n warnings={data?.warnings}\n warningStyle={data?.warningStyle}\n isDark={isDark}\n />\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flex: 1,\n }}\n >\n {data?.emptyMessage ?? emptyState}\n </Box>\n </Box>\n );\n }\n\n // Empty state WITHOUT toolbar (default)\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n height: \"100%\",\n }}\n >\n {emptyState}\n </Box>\n );\n }\n\n // Render based on wrapper type\n if (screenshotWrapper === \"grid\") {\n return (\n <Box sx={{ display: \"flex\", flexDirection: \"column\", height: \"100%\" }}>\n {data.header}\n <ToolbarArea\n toolbar={data.toolbar}\n warnings={data.warnings}\n warningStyle={data.warningStyle}\n isDark={isDark}\n />\n <ScreenshotDataGrid\n ref={ref as Ref<DataGridHandle>}\n style={{\n blockSize: \"auto\",\n maxHeight: \"100%\",\n overflow: \"auto\",\n fontSize: \"0.875rem\",\n borderWidth: 1,\n }}\n columns={(data.columns ?? []) as never}\n rows={(data.rows ?? []) as never}\n renderers={{\n noRowsFallback: (\n <EmptyRowsRenderer emptyMessage={data.noRowsMessage} />\n ),\n }}\n defaultColumnOptions={data.defaultColumnOptions}\n />\n {data.footer}\n </Box>\n );\n }\n\n // Box wrapper for charts\n return (\n <Box sx={{ display: \"flex\", flexDirection: \"column\", height: \"100%\" }}>\n {data.header}\n <ToolbarArea\n toolbar={data.toolbar}\n warnings={data.warnings}\n warningStyle={data.warningStyle}\n isDark={isDark}\n />\n <ScreenshotBox\n ref={ref as Ref<HTMLDivElement>}\n height=\"100%\"\n backgroundColor={isDark ? \"#1f2937\" : \"white\"}\n >\n {data.content}\n </ScreenshotBox>\n {data.footer}\n </Box>\n );\n }\n\n // Set display name for DevTools\n ResultViewInner.displayName = displayName;\n\n // Create forwardRef component with proper typing\n const ForwardedResultView = forwardRef(ResultViewInner);\n\n return ForwardedResultView;\n}\n","// @datarecce/ui/primitives - Building block components for custom composition\n// These are pure presentation components - no data fetching, just props and callbacks.\n\n\"use client\";\n\n/**\n * Version marker for the primitives surface.\n */\nexport const PRIMITIVES_API_VERSION = \"0.1.0\";\n\n// =============================================================================\n// LINEAGE PRIMITIVES\n// =============================================================================\n\n/**\n * Lineage column node primitives for column-level lineage rendering.\n *\n * @remarks\n * Exports: COLUMN_NODE_HEIGHT, COLUMN_NODE_WIDTH, ColumnTransformationType,\n * LineageColumnNode, LineageColumnNodeData, LineageColumnNodeProps.\n */\nexport {\n COLUMN_NODE_HEIGHT,\n COLUMN_NODE_WIDTH,\n type ColumnTransformationType,\n LineageColumnNode,\n type LineageColumnNodeData,\n type LineageColumnNodeProps,\n} from \"./components/lineage/columns\";\n/**\n * Lineage edge primitives for graph edges.\n *\n * @remarks\n * Exports: EdgeChangeStatus, LineageEdge, LineageEdgeData, LineageEdgeProps.\n */\nexport {\n type EdgeChangeStatus,\n LineageEdge,\n type LineageEdgeData,\n type LineageEdgeProps,\n} from \"./components/lineage/edges\";\n/**\n * Lineage legend primitives for change status and transformation types.\n *\n * @remarks\n * Exports: ChangeStatusLegendItem, LineageLegend, LineageLegendProps,\n * TransformationLegendItem.\n */\nexport {\n type ChangeStatusLegendItem,\n LineageLegend,\n type LineageLegendProps,\n type TransformationLegendItem,\n} from \"./components/lineage/legend\";\n/**\n * Lineage node primitives for graph nodes.\n *\n * @remarks\n * Exports: LineageNode, LineageNodeData, LineageNodeProps, NodeChangeStatus.\n */\nexport {\n LineageNode,\n type LineageNodeData,\n type LineageNodeProps,\n type NodeChangeStatus,\n} from \"./components/lineage/nodes\";\n\n// =============================================================================\n// CHECK PRIMITIVES\n// =============================================================================\n\n/**\n * Check action primitives for check-level actions.\n *\n * @remarks\n * Exports: CheckAction, CheckActions, CheckActionsProps, CheckActionType.\n */\nexport {\n type CheckAction,\n CheckActions,\n type CheckActionsProps,\n type CheckActionType,\n} from \"./components/check/CheckActions\";\n/**\n * Check breadcrumb primitive for inline name editing.\n *\n * @remarks\n * Exports: CheckBreadcrumb, CheckBreadcrumbProps.\n */\nexport {\n CheckBreadcrumb,\n type CheckBreadcrumbProps,\n} from \"./components/check/CheckBreadcrumb\";\n/**\n * Check card primitives for individual check display.\n *\n * @remarks\n * Exports: CheckCard, CheckCardData, CheckCardProps, CheckRunStatus, CheckType.\n */\nexport {\n CheckCard,\n type CheckCardData,\n type CheckCardProps,\n type CheckRunStatus,\n type CheckType,\n} from \"./components/check/CheckCard\";\n/**\n * Check description primitives for inline description editing.\n *\n * @remarks\n * Exports: CheckDescription, CheckDescriptionProps.\n */\nexport {\n CheckDescription,\n type CheckDescriptionProps,\n} from \"./components/check/CheckDescription\";\n/**\n * Check detail primitives for full check view content.\n *\n * @remarks\n * Exports: CheckDetail, CheckDetailProps, CheckDetailTab.\n */\nexport {\n CheckDetail,\n type CheckDetailProps,\n type CheckDetailTab,\n} from \"./components/check/CheckDetail\";\n/**\n * Check empty state primitive.\n *\n * @remarks\n * Exports: CheckEmptyState, CheckEmptyStateProps.\n */\nexport {\n CheckEmptyState,\n type CheckEmptyStateProps,\n} from \"./components/check/CheckEmptyState\";\n/**\n * Check list primitives for rendering the checks list.\n *\n * @remarks\n * Exports: CheckList, CheckListProps.\n */\nexport { CheckList, type CheckListProps } from \"./components/check/CheckList\";\n\n/**\n * Lineage diff view primitives for check results.\n *\n * @remarks\n * Exports: LineageDiffView, LineageDiffViewOptions, LineageDiffViewProps, LineageViewRef.\n */\nexport {\n LineageDiffView,\n type LineageDiffViewOptions,\n type LineageDiffViewProps,\n type LineageViewRef,\n} from \"./components/check/LineageDiffView\";\n\n/**\n * Preset check template primitives.\n *\n * @remarks\n * Exports: GenerateCheckTemplateOptions, generateCheckTemplate,\n * PresetCheckTemplateView, PresetCheckTemplateViewProps.\n */\nexport {\n type GenerateCheckTemplateOptions,\n generateCheckTemplate,\n PresetCheckTemplateView,\n type PresetCheckTemplateViewProps,\n} from \"./components/check/PresetCheckTemplateView\";\n\n/**\n * Check timeline primitives.\n *\n * @remarks\n * Exports: CommentInput, CommentInputProps, TimelineActor, TimelineEvent,\n * TimelineEventData, TimelineEventProps, TimelineEventType.\n */\nexport {\n CommentInput,\n type CommentInputProps,\n type TimelineActor,\n TimelineEvent,\n type TimelineEventData,\n type TimelineEventProps,\n type TimelineEventType,\n} from \"./components/check/timeline\";\n\n/**\n * Check utility functions.\n *\n * @remarks\n * Exports: buildCheckDescription, buildCheckTitle, formatSqlAsMarkdown,\n * isDisabledByNoResult.\n */\nexport {\n buildCheckDescription,\n buildCheckTitle,\n formatSqlAsMarkdown,\n isDisabledByNoResult,\n} from \"./components/check/utils\";\n\n// =============================================================================\n// RUN PRIMITIVES\n// =============================================================================\n\n/**\n * Run list primitives for run history display.\n *\n * @remarks\n * Exports: RunList, RunListItem, RunListItemData, RunListItemProps, RunListProps.\n */\nexport {\n RunList,\n RunListItem,\n type RunListItemData,\n type RunListItemProps,\n type RunListProps,\n} from \"./components/run/RunList\";\n\n/**\n * Run progress primitives for execution indicators.\n *\n * @remarks\n * Exports: RunProgress, RunProgressOverlay, RunProgressOverlayProps,\n * RunProgressProps, RunProgressVariant.\n */\nexport {\n RunProgress,\n RunProgressOverlay,\n type RunProgressOverlayProps,\n type RunProgressProps,\n type RunProgressVariant,\n} from \"./components/run/RunProgress\";\n\n/**\n * Run status badge primitives.\n *\n * @remarks\n * Exports: formatRunDate, formatRunDateTime, inferRunStatus, RunStatus,\n * RunStatusAndDate, RunStatusAndDateProps, RunStatusBadge, RunStatusBadgeProps,\n * RunStatusWithDate, RunStatusWithDateProps.\n */\nexport {\n formatRunDate,\n formatRunDateTime,\n inferRunStatus,\n type RunStatus,\n RunStatusAndDate,\n type RunStatusAndDateProps,\n RunStatusBadge,\n type RunStatusBadgeProps,\n RunStatusWithDate,\n type RunStatusWithDateProps,\n} from \"./components/run/RunStatusBadge\";\n\n/**\n * Run toolbar primitives for warnings and actions.\n *\n * @remarks\n * Exports: DiffViewOptions, RunToolbar, RunToolbarProps.\n */\nexport {\n type DiffViewOptions,\n RunToolbar,\n type RunToolbarProps,\n} from \"./components/run/RunToolbar\";\n\n/**\n * Run registry types for extensibility.\n *\n * @remarks\n * Exports: RefTypes, RegistryEntry, RunFormParamTypes, RunFormProps,\n * RunResultViewProps, ViewOptionTypes.\n */\nexport type {\n RefTypes,\n RegistryEntry,\n RunFormParamTypes,\n RunFormProps,\n RunResultViewProps,\n ViewOptionTypes,\n} from \"./components/run/types\";\n\n// =============================================================================\n// DATA PRIMITIVES\n// =============================================================================\n\n/**\n * Histogram chart primitives (Chart.js based).\n *\n * @remarks\n * Exports: ChartBarColors, ChartThemeColors, getChartBarColors,\n * getChartThemeColors, HistogramChart, HistogramChartProps, HistogramDataset,\n * HistogramDataType.\n */\nexport {\n type ChartBarColors,\n type ChartThemeColors,\n getChartBarColors,\n getChartThemeColors,\n HistogramChart,\n type HistogramChartProps,\n type HistogramDataset,\n type HistogramDataType,\n} from \"./components/data/HistogramChart\";\n/**\n * Screenshot data grid primitives (AG Grid wrapper).\n *\n * @remarks\n * Exports: ColDef, ColGroupDef, DataGridHandle, DataGridRow, EmptyRowsRenderer,\n * EmptyRowsRendererProps, GetRowIdParams, GridReadyEvent, RecceDataGridHandle,\n * ScreenshotDataGrid, ScreenshotDataGridProps.\n */\nexport {\n type ColDef,\n type ColGroupDef,\n type DataGridHandle,\n type DataGridRow,\n EmptyRowsRenderer,\n type EmptyRowsRendererProps,\n type GetRowIdParams,\n type GridReadyEvent,\n type RecceDataGridHandle,\n ScreenshotDataGrid,\n type ScreenshotDataGridProps,\n} from \"./components/data/ScreenshotDataGrid\";\n\n/**\n * Top-K bar chart primitives (value distribution).\n *\n * @remarks\n * Exports: TopKBarChart, TopKBarChartProps, TopKDataset, TopKItem.\n */\nexport {\n TopKBarChart,\n type TopKBarChartProps,\n type TopKDataset,\n type TopKItem,\n} from \"./components/data/TopKBarChart\";\n\n// =============================================================================\n// SCHEMA PRIMITIVES\n// =============================================================================\n\n/**\n * Schema diff types for base vs current comparison.\n *\n * @remarks\n * Exports: SchemaDiffRow, SchemaDiffStatus.\n */\nexport type {\n SchemaDiffRow,\n SchemaDiffStatus,\n} from \"./components/schema/types\";\n\n// =============================================================================\n// EDITOR PRIMITIVES\n// =============================================================================\n\n/**\n * Code editor primitives (CodeMirror based).\n *\n * @remarks\n * Exports: CodeEditor, CodeEditorLanguage, CodeEditorProps, CodeEditorTheme.\n */\nexport {\n CodeEditor,\n type CodeEditorLanguage,\n type CodeEditorProps,\n type CodeEditorTheme,\n} from \"./components/editor/CodeEditor\";\n\n/**\n * Diff editor primitives (CodeMirror merge view).\n *\n * @remarks\n * Exports: DiffEditor, DiffEditorLanguage, DiffEditorProps, DiffEditorTheme.\n */\nexport {\n DiffEditor,\n type DiffEditorLanguage,\n type DiffEditorProps,\n type DiffEditorTheme,\n} from \"./components/editor/DiffEditor\";\n\n// =============================================================================\n// UI PRIMITIVES\n// =============================================================================\n\n/**\n * Changed-only checkbox primitive for filtering diff results.\n *\n * @remarks\n * Exports: ChangedOnlyCheckbox, ChangedOnlyCheckboxProps.\n */\nexport {\n ChangedOnlyCheckbox,\n type ChangedOnlyCheckboxProps,\n} from \"./components/ui/ChangedOnlyCheckbox\";\n/**\n * Diff display mode switch primitive for toggling inline/side-by-side views.\n *\n * @remarks\n * Exports: DiffDisplayMode, DiffDisplayModeSwitch, DiffDisplayModeSwitchProps.\n */\nexport {\n type DiffDisplayMode,\n DiffDisplayModeSwitch,\n type DiffDisplayModeSwitchProps,\n} from \"./components/ui/DiffDisplayModeSwitch\";\n/**\n * Diff text primitives for inline diff visualization.\n *\n * @remarks\n * Exports: DiffText, DiffTextProps.\n */\nexport { DiffText, type DiffTextProps } from \"./components/ui/DiffText\";\n/**\n * Diff text with toast primitive - DiffText with copy notification feedback.\n *\n * @remarks\n * Exports: DiffTextWithToast, DiffTextWithToastProps.\n */\nexport {\n DiffTextWithToast,\n type DiffTextWithToastProps,\n} from \"./components/ui/DiffTextWithToast\";\n/**\n * Dropdown values input primitives (multi-select with filtering).\n *\n * @remarks\n * Exports: DropdownValuesInput, DropdownValuesInputProps, DropdownValuesInputSize.\n */\nexport {\n DropdownValuesInput,\n type DropdownValuesInputProps,\n type DropdownValuesInputSize,\n} from \"./components/ui/DropdownValuesInput\";\n/**\n * Empty state primitives.\n *\n * @remarks\n * Exports: EmptyState, EmptyStateProps.\n */\nexport { EmptyState, type EmptyStateProps } from \"./components/ui/EmptyState\";\n/**\n * External link confirmation dialog primitives.\n *\n * @remarks\n * Exports: ExternalLinkConfirmDialog, ExternalLinkConfirmDialogProps, truncateUrl.\n */\nexport {\n ExternalLinkConfirmDialog,\n type ExternalLinkConfirmDialogProps,\n truncateUrl,\n} from \"./components/ui/ExternalLinkConfirmDialog\";\n/**\n * Markdown content primitives.\n *\n * @remarks\n * Exports: MarkdownContent, MarkdownContentProps.\n */\nexport {\n MarkdownContent,\n type MarkdownContentProps,\n} from \"./components/ui/MarkdownContent\";\n/**\n * Screenshot box primitives.\n *\n * @remarks\n * Exports: ScreenshotBox, ScreenshotBoxProps.\n */\nexport {\n ScreenshotBox,\n type ScreenshotBoxProps,\n} from \"./components/ui/ScreenshotBox\";\n/**\n * Split pane primitives (resizable layout).\n *\n * @remarks\n * Exports: HSplit, SplitProps, VSplit.\n */\nexport { HSplit, type SplitProps, VSplit } from \"./components/ui/Split\";\n/**\n * Split pane primitives with explicit direction.\n *\n * @remarks\n * Exports: SplitDirection, SplitPane, SplitPaneProps.\n */\nexport {\n type SplitDirection,\n SplitPane,\n type SplitPaneProps,\n} from \"./components/ui/SplitPane\";\n/**\n * Toast notification system primitives.\n *\n * @remarks\n * Exports: Toaster, ToasterProvider, ToastOptions, toaster, useToaster.\n */\nexport {\n Toaster,\n ToasterProvider,\n type ToastOptions,\n toaster,\n useToaster,\n} from \"./components/ui/Toaster\";\n/**\n * Toggle switch primitive for boolean value inputs.\n *\n * @remarks\n * Exports: ToggleSwitch, ToggleSwitchProps.\n */\nexport {\n ToggleSwitch,\n type ToggleSwitchProps,\n} from \"./components/ui/ToggleSwitch\";\n\n// =============================================================================\n// RESULT VIEW PRIMITIVES\n// =============================================================================\n// NOTE: Result view factory canonical in @datarecce/ui/result\n\n/**\n * Result view factory primitives.\n * @deprecated Import from @datarecce/ui/result instead\n */\nexport {\n type CreatedResultViewProps,\n createResultView,\n type ResultViewConfig,\n type ResultViewData,\n type ResultViewProps,\n type ResultViewRef,\n type ResultViewTransformOptions,\n type ScreenshotWrapperType,\n} from \"./components/result\";\n","\"use client\";\n\n/**\n * @file HistogramResultView.tsx\n * @description Framework-agnostic Histogram result view components for @datarecce/ui\n *\n * These components use the createResultView factory pattern and can be used by both\n * Recce OSS and Recce Cloud. They accept generic Run types and use type guards\n * for validation.\n *\n * The components display histogram diff data as a chart:\n * - HistogramDiffResultView: Diff between base and current histogram distributions\n */\n\nimport Box from \"@mui/material/Box\";\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n type HistogramDiffParams,\n type HistogramDiffResult,\n isHistogramDiffRun,\n type Run,\n} from \"../../api\";\nimport { HistogramChart, type HistogramDataType } from \"../../primitives\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with histogram_diff result\n */\nexport type HistogramDiffRun = Run & {\n type: \"histogram_diff\";\n params?: HistogramDiffParams;\n result?: HistogramDiffResult;\n};\n\n/**\n * Props for HistogramDiffResultView component\n */\nexport interface HistogramResultViewProps\n extends CreatedResultViewProps<unknown> {\n run: HistogramDiffRun | unknown;\n}\n\n// ============================================================================\n// Type Guard (wrapper to accept unknown)\n// ============================================================================\n\nfunction isHistogramDiffRunGuard(run: unknown): run is HistogramDiffRun {\n return isHistogramDiffRun(run as Run);\n}\n\n// ============================================================================\n// Factory-Created Component\n// ============================================================================\n\n/**\n * Result view for histogram diff comparison between base and current environments\n *\n * Displays a chart comparing histogram distributions between base and current.\n * The chart title shows the model and column name.\n *\n * @example\n * ```tsx\n * <HistogramDiffResultView run={histogramDiffRun} ref={boxRef} />\n * ```\n */\nexport const HistogramDiffResultView = createResultView<\n HistogramDiffRun,\n unknown,\n HTMLDivElement\n>({\n displayName: \"HistogramDiffResultView\",\n typeGuard: isHistogramDiffRunGuard,\n expectedRunType: \"histogram_diff\",\n screenshotWrapper: \"box\",\n conditionalEmptyState: (run) => {\n const base = run.result?.base;\n const current = run.result?.current;\n if (!base || !current) {\n return <div>Loading...</div>;\n }\n return null;\n },\n transformData: (run): ResultViewData | null => {\n const params = run.params as HistogramDiffParams;\n const base = run.result?.base;\n const current = run.result?.current;\n const min = run.result?.min;\n const max = run.result?.max;\n const binEdges = run.result?.bin_edges ?? [];\n\n // This shouldn't happen due to conditionalEmptyState, but type safety\n if (!base || !current) {\n return { isEmpty: true };\n }\n\n // Map column_type to HistogramDataType\n const columnType = (run.params?.column_type ?? \"numeric\") as string;\n const dataType: HistogramDataType =\n columnType === \"datetime\"\n ? \"datetime\"\n : columnType === \"string\"\n ? \"string\"\n : \"numeric\";\n\n return {\n content: (\n <Box sx={{ display: \"flex\", flexDirection: \"row\" }}>\n <Box sx={{ flex: 1 }} />\n <Box sx={{ width: \"80%\", height: \"35vh\", m: 4 }}>\n <HistogramChart\n title={`Model ${params.model}.${params.column_name}`}\n dataType={dataType}\n baseData={{ counts: base.counts }}\n currentData={{ counts: current.counts }}\n min={min}\n max={max}\n samples={base.total}\n binEdges={binEdges}\n />\n </Box>\n <Box sx={{ flex: 1 }} />\n </Box>\n ),\n };\n },\n}) as ForwardRefExoticComponent<\n HistogramResultViewProps & RefAttributes<HTMLDivElement>\n>;\n","\"use client\";\n\n/**\n * @file ColumnNameCell.tsx\n * @description A cell renderer component for displaying column names in schema tables.\n *\n * This component renders a column name with an optional context menu for performing\n * diff operations (Profile Diff, Histogram Diff, Top-k Diff, Value Diff) on the column.\n * It also displays a loading spinner when column-level lineage is being computed.\n *\n * @example\n * ```tsx\n * <ColumnNameCell\n * model={nodeData}\n * row={schemaDiffRow}\n * singleEnv={false}\n * cllRunning={false}\n * showMenu={true}\n * />\n * ```\n */\n\nimport Box from \"@mui/material/Box\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport IconButton from \"@mui/material/IconButton\";\nimport ListSubheader from \"@mui/material/ListSubheader\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport { type MouseEvent, useState } from \"react\";\nimport { VscKebabVertical } from \"react-icons/vsc\";\nimport type { NodeData } from \"../../api\";\nimport {\n useLineageGraphContext,\n useLineageViewContext,\n useRecceActionContext,\n useRecceInstanceContext,\n} from \"../../contexts\";\nimport { supportsHistogramDiff } from \"../histogram\";\nimport { buildColumnTooltip, DataTypeIcon } from \"../ui/DataTypeIcon\";\nimport type { SchemaDiffRow } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Props for the ColumnNameCell component\n */\nexport interface ColumnNameCellProps {\n /** The model/node data containing column information */\n model: NodeData;\n /** The schema diff row data for this column */\n row: SchemaDiffRow;\n /** Whether viewing a single environment (disables diff menu) */\n singleEnv?: boolean;\n /** Whether column-level lineage is currently being computed */\n cllRunning?: boolean;\n /** Whether to show the context menu (defaults to true) */\n showMenu?: boolean;\n /** Callback when user clicks a definition-changed badge to view SQL diff */\n onViewCode?: () => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * ColumnNameCell - Renders a column name with optional diff action menu\n *\n * Displays the column name with:\n * - A context menu for initiating diff operations (when applicable)\n * - A loading spinner when column-level lineage is running\n * - Tooltip indicating column lineage viewing capability\n *\n * The menu is hidden when:\n * - showMenu is false\n * - singleEnv is true (no comparison available)\n * - The model is a source (sources don't support diff operations)\n */\nexport function ColumnNameCell({\n model,\n row,\n singleEnv,\n cllRunning,\n showMenu = true,\n onViewCode,\n}: ColumnNameCellProps) {\n const lineageViewContext = useLineageViewContext();\n const { isActionAvailable } = useLineageGraphContext();\n const { runAction } = useRecceActionContext();\n const { featureToggles } = useRecceInstanceContext();\n const {\n name,\n baseType,\n currentType,\n baseIndex,\n currentIndex,\n reordered,\n definitionChanged,\n } = row;\n const columnType =\n currentType ??\n baseType ??\n ((row as Record<string, unknown>).type as string | undefined);\n const isAdded = baseIndex === undefined && currentIndex !== undefined;\n const isRemoved = baseIndex !== undefined && currentIndex === undefined;\n const isTypeChanged = !isAdded && !isRemoved && baseType !== currentType;\n const hasStructuralChange =\n !isAdded && !isRemoved && (baseType !== currentType || reordered === true);\n\n const columnStatus = singleEnv\n ? \"unchanged\"\n : isAdded\n ? \"added\"\n : isRemoved\n ? \"removed\"\n : isTypeChanged\n ? \"type_changed\"\n : definitionChanged\n ? \"definition_changed\"\n : \"unchanged\";\n\n const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);\n const menuOpen = Boolean(anchorEl);\n\n const handleMenuClick = (event: MouseEvent<HTMLElement>) => {\n event.stopPropagation();\n setAnchorEl(event.currentTarget);\n };\n\n const handleMenuClose = () => {\n setAnchorEl(null);\n };\n\n const handleProfileDiff = () => {\n runAction(\n \"profile_diff\",\n { model: model.name, columns: [name] },\n {\n showForm: false,\n trackProps: {\n action: \"profile_diff\",\n source: \"schema_column_menu\",\n node_count: 1,\n },\n },\n );\n };\n\n const handleHistogramDiff = () => {\n runAction(\n \"histogram_diff\",\n { model: model.name, column_name: name, column_type: columnType },\n {\n showForm: false,\n trackProps: {\n action: \"histogram_diff\",\n source: \"schema_column_menu\",\n node_count: 1,\n },\n },\n );\n };\n\n const handleTopkDiff = () => {\n runAction(\n \"top_k_diff\",\n { model: model.name, column_name: name, k: 50 },\n {\n showForm: false,\n trackProps: {\n action: \"top_k_diff\",\n source: \"schema_column_menu\",\n node_count: 1,\n },\n },\n );\n };\n\n const handleValueDiff = () => {\n runAction(\n \"value_diff\",\n { model: model.name, columns: [name] },\n {\n showForm: true,\n trackProps: {\n action: \"value_diff\",\n source: \"schema_column_menu\",\n node_count: 1,\n },\n },\n );\n };\n\n const addedOrRemoved = !baseType || !currentType;\n const isCllDisabled =\n lineageViewContext === undefined ||\n !isActionAvailable(\"change_analysis\") ||\n (baseIndex !== undefined && currentIndex === undefined);\n\n const tooltipTitle = buildColumnTooltip({\n name,\n status: columnStatus,\n baseType,\n currentType,\n cllAvailable: !isCllDisabled,\n });\n\n return (\n <Tooltip title={tooltipTitle} placement=\"top\">\n <Box sx={{ display: \"flex\", alignItems: \"center\", gap: \"3px\" }}>\n {hasStructuralChange && (\n <span className=\"schema-change-badge schema-change-badge-changed\">\n ~\n </span>\n )}\n {isAdded && (\n <span className=\"schema-change-badge schema-change-badge-added\">\n +\n </span>\n )}\n {isRemoved && (\n <span className=\"schema-change-badge schema-change-badge-removed\">\n -\n </span>\n )}\n {definitionChanged && (\n <Tooltip\n title=\"Definition changed — click to view code\"\n placement=\"top\"\n onMouseOver={(e) => e.stopPropagation()}\n >\n {onViewCode ? (\n <button\n type=\"button\"\n className=\"schema-change-badge schema-change-badge-changed schema-change-badge-clickable\"\n onClick={(e) => {\n e.stopPropagation();\n onViewCode();\n }}\n >\n ~\n </button>\n ) : (\n <span className=\"schema-change-badge schema-change-badge-changed\">\n ~\n </span>\n )}\n </Tooltip>\n )}\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {name}\n </Box>\n {isTypeChanged ? (\n <Box\n component=\"span\"\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"2px\",\n ml: \"4px\",\n }}\n >\n {baseType && (\n <Box\n component=\"span\"\n sx={{ textDecoration: \"line-through\", opacity: 0.6 }}\n >\n <DataTypeIcon type={baseType} size={16} disableTooltip />\n </Box>\n )}\n <Box component=\"span\" sx={{ fontSize: \"0.7em\", opacity: 0.5 }}>\n →\n </Box>\n {currentType && (\n <DataTypeIcon type={currentType} size={16} disableTooltip />\n )}\n </Box>\n ) : (\n columnType && (\n <Box component=\"span\" sx={{ ml: \"4px\" }}>\n <DataTypeIcon type={columnType} size={16} disableTooltip />\n </Box>\n )\n )}\n <Box sx={{ flex: 1 }} />\n {cllRunning && <CircularProgress size={12} color=\"inherit\" />}\n {showMenu && !singleEnv && model.resource_type !== \"source\" && (\n <>\n <IconButton\n aria-label=\"Column options\"\n className=\"row-context-menu\"\n size=\"small\"\n disabled={featureToggles.disableDatabaseQuery}\n onClick={handleMenuClick}\n >\n <VscKebabVertical />\n </IconButton>\n <Menu\n anchorEl={anchorEl}\n open={menuOpen}\n onClose={handleMenuClose}\n slotProps={{\n list: { sx: { lineHeight: \"20px\" } },\n }}\n >\n <ListSubheader sx={{ m: 0, p: \"4px 12px\", lineHeight: \"20px\" }}>\n Diff\n </ListSubheader>\n <MenuItem\n onClick={() => {\n handleProfileDiff();\n handleMenuClose();\n }}\n disabled={addedOrRemoved}\n sx={{ fontSize: \"0.85rem\" }}\n >\n Profile Diff\n </MenuItem>\n <MenuItem\n onClick={() => {\n handleHistogramDiff();\n handleMenuClose();\n }}\n disabled={\n addedOrRemoved ||\n (columnType ? !supportsHistogramDiff(columnType) : true)\n }\n sx={{ fontSize: \"0.85rem\" }}\n >\n Histogram Diff\n </MenuItem>\n <MenuItem\n onClick={() => {\n handleTopkDiff();\n handleMenuClose();\n }}\n disabled={addedOrRemoved}\n sx={{ fontSize: \"0.85rem\" }}\n >\n Top-k Diff\n </MenuItem>\n <MenuItem\n onClick={() => {\n handleValueDiff();\n handleMenuClose();\n }}\n disabled={addedOrRemoved}\n sx={{ fontSize: \"0.85rem\" }}\n >\n Value Diff\n </MenuItem>\n </Menu>\n </>\n )}\n </Box>\n </Tooltip>\n );\n}\n","\"use client\";\n\n/**\n * @file schemaCells.tsx\n * @description Cell components and render functions for Schema grid views\n */\n\nimport type { ICellRendererParams } from \"ag-grid-community\";\nimport React from \"react\";\nimport type { NodeData, RowObjectType } from \"../../../api\";\nimport type { SchemaDiffRow, SchemaRow } from \"../../schema\";\nimport { ColumnNameCell } from \"../../schema/ColumnNameCell\";\n\n// ============================================================================\n// Render Functions for toSchemaDataGrid.ts\n// ============================================================================\n\n/**\n * Creates a cellRenderer function for schema diff column names\n */\nexport function createSchemaColumnNameRenderer(\n node: NodeData,\n cllRunningMap?: Map<string, boolean>,\n showMenu?: boolean,\n onViewCode?: () => void,\n): (params: ICellRendererParams<SchemaDiffRow>) => React.ReactNode {\n return (params) => {\n const row = params.data;\n if (!row) return null;\n return (\n <ColumnNameCell\n model={node}\n row={row}\n cllRunning={cllRunningMap?.get(row.name) ?? false}\n showMenu={showMenu}\n onViewCode={onViewCode}\n />\n );\n };\n}\n\n/**\n * Creates a cellRenderer function for single-env schema column names\n */\nexport function createSingleEnvColumnNameRenderer(\n node: NodeData,\n cllRunningMap?: Map<string, boolean>,\n showMenu?: boolean,\n): (params: ICellRendererParams<SchemaRow>) => React.ReactNode {\n return (params) => {\n const row = params.data;\n if (!row) return null;\n return (\n <ColumnNameCell\n model={node}\n row={row}\n cllRunning={cllRunningMap?.get(row.name) ?? false}\n singleEnv\n showMenu={showMenu}\n />\n );\n };\n}\n\n// ============================================================================\n// Merged Column Render Functions for Compressed Schema View\n// ============================================================================\n\n/**\n * Renders the merged index column.\n * Shows currentIndex for normal/added rows, baseIndex for removed rows.\n * For reordered rows, shows strikethrough old → bold new.\n */\nexport function renderIndexCell(\n params: ICellRendererParams<RowObjectType>,\n): React.ReactNode {\n if (!params.data) {\n return null;\n }\n const row = params.data;\n\n const { baseIndex, currentIndex, reordered } = row;\n const isRemoved = currentIndex === undefined;\n\n if (\n reordered &&\n baseIndex !== undefined &&\n currentIndex !== undefined &&\n baseIndex !== currentIndex\n ) {\n return (\n <span>\n <span className=\"schema-index-old\">{baseIndex}</span>\n <span className=\"schema-index-new\">{currentIndex}</span>\n </span>\n );\n }\n\n const value = isRemoved ? (baseIndex ?? \"-\") : (currentIndex ?? \"-\");\n return <span>{value}</span>;\n}\n","/**\n * @file toSchemaDataGrid.ts\n * @description Grid generator for schema diff and single-environment schema views\n *\n * This file is intentionally .ts (not .tsx) - all JSX rendering is delegated\n * to schemaCells.tsx via render functions.\n */\n\nimport \"../../../components/schema/style.css\";\nimport type { ColDef, ColGroupDef } from \"ag-grid-community\";\nimport {\n type NodeColumnData,\n type NodeData,\n type RowObjectType,\n} from \"../../../api\";\nimport {\n createSchemaColumnNameRenderer,\n createSingleEnvColumnNameRenderer,\n renderIndexCell,\n} from \"../../../components/ui/dataGrid/schemaCells\";\nimport { mergeKeysWithStatus } from \"../../../utils\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface SchemaDiffRow extends RowObjectType {\n name: string;\n reordered?: boolean;\n currentIndex?: number;\n baseIndex?: number;\n currentType?: string;\n baseType?: string;\n /** True when the column's SQL definition changed but name/type stayed the same */\n definitionChanged?: boolean;\n}\n\nexport interface SchemaRow extends RowObjectType {\n name: string;\n index: number;\n type?: string;\n}\n\ntype SchemaDiff = Record<string, SchemaDiffRow>;\n\nexport interface SchemaDataGridOptions {\n /** Node data for context menu actions */\n node?: NodeData;\n /** Map of column names to CLL loading state */\n cllRunningMap?: Map<string, boolean>;\n /** Whether to show the column action menu (default: true) */\n showMenu?: boolean;\n /** Per-column change status from breaking change analysis */\n columnChanges?: Record<string, \"added\" | \"removed\" | \"modified\"> | null;\n /** Callback when user clicks a definition-changed badge to view SQL diff */\n onViewCode?: () => void;\n}\n\nexport interface SchemaDataGridResult {\n columns: (ColDef<SchemaDiffRow> | ColGroupDef<SchemaDiffRow>)[];\n rows: SchemaDiffRow[];\n}\n\nexport interface SingleEnvSchemaDataGridResult {\n columns: (ColDef<SchemaRow> | ColGroupDef<SchemaRow>)[];\n rows: SchemaRow[];\n}\n\n// ============================================================================\n// Data Transformation\n// ============================================================================\n\n/**\n * Merges base and current column schemas into a diff structure\n */\nexport function mergeColumns(\n baseColumns: NodeData[\"columns\"] = {},\n currentColumns: NodeData[\"columns\"] = {},\n): SchemaDiff {\n const result: SchemaDiff = {};\n const mergedStatus = mergeKeysWithStatus(\n Object.keys(baseColumns),\n Object.keys(currentColumns),\n );\n\n Object.entries(mergedStatus).forEach(([name, status]) => {\n result[name] = {\n name,\n reordered: status === \"reordered\",\n __status: undefined,\n };\n });\n\n let filteredIndex = 0;\n Object.entries(baseColumns).forEach(([name, column]) => {\n if (column != null) {\n result[name].baseIndex = filteredIndex += 1;\n result[name].baseType = column.type;\n }\n });\n\n filteredIndex = 0;\n Object.entries(currentColumns).forEach(([name, column]) => {\n if (column != null) {\n result[name].currentIndex = filteredIndex += 1;\n result[name].currentType = column.type;\n }\n });\n\n return result;\n}\n\n// ============================================================================\n// Main Generator Functions\n// ============================================================================\n\n/**\n * Generates grid configuration for schema diff view\n * Uses merged columns: Index (merged base/current), Name (with inline DataTypeIcon)\n */\nexport function toSchemaDataGrid(\n schemaDiff: SchemaDiff,\n options: SchemaDataGridOptions = {},\n): SchemaDataGridResult {\n const { node, cllRunningMap, showMenu, columnChanges, onViewCode } = options;\n\n const columns: ColDef<SchemaDiffRow>[] = [\n {\n field: \"index\",\n headerName: \"\",\n resizable: true,\n minWidth: 35,\n width: 35,\n cellRenderer: renderIndexCell,\n cellClass: \"schema-column schema-column-index\",\n },\n {\n field: \"name\",\n headerName: \"Name\",\n resizable: true,\n cellRenderer: node\n ? createSchemaColumnNameRenderer(\n node,\n cllRunningMap,\n showMenu,\n onViewCode,\n )\n : undefined,\n cellClass: \"schema-column\",\n // Include definitionChanged in the value so ag-grid re-renders the cell\n // when the badge state changes (e.g., after Impact Radius completes)\n valueGetter: (params) => {\n const row = params.data;\n return row ? `${row.name}|${row.definitionChanged ?? false}` : \"\";\n },\n },\n ];\n\n const rows = Object.values(schemaDiff);\n\n // Mark columns whose SQL definition changed but have no other visible change\n if (columnChanges) {\n for (const row of rows) {\n const isAdded = row.baseIndex === undefined;\n const isRemoved = row.currentIndex === undefined;\n const isTypeChanged =\n !isAdded && !isRemoved && row.baseType !== row.currentType;\n const changeStatus = columnChanges[row.name];\n\n if (\n changeStatus === \"modified\" &&\n !isAdded &&\n !isRemoved &&\n !isTypeChanged &&\n !row.reordered\n ) {\n row.definitionChanged = true;\n }\n }\n }\n\n return { columns, rows };\n}\n\n/**\n * Generates grid configuration for single-environment schema view\n */\nexport function toSingleEnvDataGrid(\n nodeColumns: NodeData[\"columns\"] = {},\n options: SchemaDataGridOptions = {},\n): SingleEnvSchemaDataGridResult {\n const { node, cllRunningMap, showMenu } = options;\n\n const nodeColumnList = Object.entries(nodeColumns).filter(\n ([_, column]) => column != null,\n ) as [string, NodeColumnData][];\n\n const rows: SchemaRow[] = nodeColumnList.map(([name, column], index) => ({\n name,\n index: index + 1,\n type: column.type,\n __status: undefined,\n }));\n\n const columns: ColDef<SchemaRow>[] = [\n {\n field: \"index\",\n headerName: \"\",\n resizable: true,\n minWidth: 35,\n width: 35,\n cellClass: \"schema-column schema-column-index\",\n },\n {\n field: \"name\",\n headerName: \"Name\",\n resizable: true,\n cellRenderer: node\n ? createSingleEnvColumnNameRenderer(node, cllRunningMap, showMenu)\n : undefined,\n cellClass: \"schema-column\",\n },\n ];\n\n return { columns, rows };\n}\n","/**\n * @file valueDiffCells.tsx\n * @description Cell components and render functions for Value Diff summary grid\n *\n * Provides specialized cell renderers for the value diff summary view:\n * - PrimaryKeyIndicatorCell: Shows key icon for primary key columns\n * - ValueDiffColumnNameCell: Column name with context menu for drill-down\n * - MatchedPercentCell: Formatted percentage display\n *\n * Also exports render functions for use in toValueDataGrid.ts generator.\n */\n\nimport Box from \"@mui/material/Box\";\nimport IconButton from \"@mui/material/IconButton\";\nimport ListSubheader from \"@mui/material/ListSubheader\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport type { ICellRendererParams } from \"ag-grid-community\";\nimport React, { type MouseEvent, useState } from \"react\";\nimport { PiDotsThreeVertical } from \"react-icons/pi\";\nimport { VscKey } from \"react-icons/vsc\";\nimport { type RowObjectType, type ValueDiffParams } from \"../../../api\";\nimport {\n type RecceActionOptions,\n useRecceActionContext,\n useRecceInstanceContext,\n} from \"../../../contexts\";\n\n// ============================================================================\n// PrimaryKeyIndicatorCell\n// ============================================================================\n\nexport interface PrimaryKeyIndicatorCellProps {\n /** The column name to check */\n columnName: string;\n /** List of primary key column names */\n primaryKeys: string[];\n}\n\n/**\n * Cell component that displays a key icon for primary key columns\n *\n * @example\n * <PrimaryKeyIndicatorCell\n * columnName=\"user_id\"\n * primaryKeys={[\"user_id\", \"order_id\"]}\n * />\n */\nexport function PrimaryKeyIndicatorCell({\n columnName,\n primaryKeys,\n}: PrimaryKeyIndicatorCellProps) {\n const isPrimaryKey = primaryKeys.includes(columnName);\n\n return (\n <Box\n sx={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n height: \"100%\",\n }}\n >\n {isPrimaryKey && <VscKey />}\n </Box>\n );\n}\n\n// ============================================================================\n// ValueDiffColumnNameCell\n// ============================================================================\n\nexport interface ValueDiffColumnNameCellProps {\n /** The column name to display */\n column: string;\n /** Parameters from the value_diff run */\n params: ValueDiffParams;\n}\n\n/**\n * Cell component for column names with context menu for drill-down actions\n *\n * @description Renders the column name with a context menu that provides:\n * - \"Show mismatched values...\" - Opens form to configure detail view\n * - \"Show mismatched values for '{column}'\" - Directly shows mismatches for this column\n *\n * @example\n * <ValueDiffColumnNameCell\n * column=\"email\"\n * params={{ model: \"users\", primary_key: \"id\" }}\n * />\n */\nexport function ValueDiffColumnNameCell({\n params,\n column,\n}: ValueDiffColumnNameCellProps) {\n const { runAction } = useRecceActionContext();\n const { featureToggles } = useRecceInstanceContext();\n const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);\n const menuOpen = Boolean(anchorEl);\n\n const handleMenuClick = (event: MouseEvent<HTMLElement>) => {\n setAnchorEl(event.currentTarget);\n };\n\n const handleMenuClose = () => {\n setAnchorEl(null);\n };\n\n const handleValueDiffDetail = (\n paramsOverride?: Partial<ValueDiffParams>,\n options?: RecceActionOptions,\n ) => {\n const newParams = {\n ...params,\n ...paramsOverride,\n };\n\n runAction(\"value_diff_detail\", newParams, options);\n };\n\n return (\n <Box sx={{ display: \"flex\" }}>\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {column}\n </Box>\n <Box sx={{ flex: 1 }} />\n\n <IconButton\n aria-label=\"Column options\"\n className=\"row-context-menu\"\n size=\"small\"\n disabled={featureToggles.disableDatabaseQuery}\n onClick={handleMenuClick}\n >\n <PiDotsThreeVertical />\n </IconButton>\n <Menu\n anchorEl={anchorEl}\n open={menuOpen}\n onClose={handleMenuClose}\n slotProps={{\n list: { sx: { lineHeight: \"20px\" } },\n }}\n >\n <ListSubheader sx={{ fontSize: \"8pt\", lineHeight: \"20px\" }}>\n Action\n </ListSubheader>\n <MenuItem\n onClick={() => {\n handleValueDiffDetail({}, { showForm: true });\n handleMenuClose();\n }}\n sx={{ fontSize: \"10pt\" }}\n >\n Show mismatched values...\n </MenuItem>\n <MenuItem\n onClick={() => {\n handleValueDiffDetail({ columns: [column] }, { showForm: false });\n handleMenuClose();\n }}\n sx={{ fontSize: \"10pt\" }}\n >\n Show mismatched values for '{column}'\n </MenuItem>\n </Menu>\n </Box>\n );\n}\n\n// ============================================================================\n// MatchedPercentCell\n// ============================================================================\n\nexport interface MatchedPercentCellProps {\n /** The percentage value (0-1 scale) */\n value: number | undefined | null;\n}\n\n/**\n * Cell component for displaying match percentage with special formatting\n *\n * @description Formats the percentage value with special handling for edge cases:\n * - Values > 99.99% but < 100%: Shows \"~99.99 %\"\n * - Values > 0% but < 0.01%: Shows \"~0.01 %\"\n * - null/undefined: Shows \"N/A\"\n * - Other values: Shows \"XX.XX %\"\n *\n * @example\n * <MatchedPercentCell value={0.9542} /> // \"95.42 %\"\n * <MatchedPercentCell value={0.99999} /> // \"~99.99 %\"\n * <MatchedPercentCell value={null} /> // \"N/A\"\n */\nexport function MatchedPercentCell({ value }: MatchedPercentCellProps) {\n let displayValue = \"N/A\";\n\n if (value != null) {\n if (value > 0.9999 && value < 1) {\n displayValue = \"~99.99 %\";\n } else if (value < 0.0001 && value > 0) {\n displayValue = \"~0.01 %\";\n } else {\n displayValue = `${(value * 100).toFixed(2)} %`;\n }\n }\n\n return <Box sx={{ textAlign: \"right\" }}>{displayValue}</Box>;\n}\n\n// ============================================================================\n// Render Functions for toValueDataGrid.ts\n// ============================================================================\n\n/**\n * Creates a cellRenderer function for the primary key indicator column\n *\n * @param primaryKeys - List of primary key column names\n * @returns A cellRenderer function compatible with AG Grid\n */\nexport function createPrimaryKeyIndicatorRenderer(\n primaryKeys: string[],\n): (params: ICellRendererParams<RowObjectType>) => React.ReactNode {\n return (params) => {\n const row = params.data;\n if (!row) return null;\n return (\n <PrimaryKeyIndicatorCell\n columnName={String(row[\"0\"])}\n primaryKeys={primaryKeys}\n />\n );\n };\n}\n\n/**\n * Creates a cellRenderer function for the column name column\n *\n * @param params - ValueDiffParams from the run\n * @returns A cellRenderer function compatible with AG Grid\n */\nexport function createColumnNameRenderer(\n params: ValueDiffParams,\n): (cellParams: ICellRendererParams<RowObjectType>) => React.ReactNode {\n return (cellParams) => {\n const row = cellParams.data;\n const field = cellParams.colDef?.field ?? \"\";\n if (!row) return null;\n return (\n <ValueDiffColumnNameCell column={String(row[field])} params={params} />\n );\n };\n}\n\n/**\n * cellRenderer function for the matched percentage column\n *\n * @param params - ICellRendererParams from AG Grid\n * @returns React node displaying formatted percentage\n */\nexport function renderMatchedPercentCell(\n params: ICellRendererParams<RowObjectType>,\n): React.ReactNode {\n const row = params.data;\n const field = params.colDef?.field ?? \"\";\n if (!row) return null;\n return <MatchedPercentCell value={row[field] as number} />;\n}\n","/**\n * @file toValueDataGrid.ts\n * @description Grid generator for Value Diff summary view\n *\n * Generates columns and rows for displaying column-level match statistics\n * from a value_diff run. This shows a summary table where each row\n * represents a column's match count and percentage.\n *\n * NOTE: This is different from toValueDiffGrid which handles row-level diffs.\n * This generator is for the summary view showing column match statistics.\n *\n * This file is intentionally .ts (not .tsx) - all JSX rendering is delegated\n * to valueDiffCells.tsx via render functions.\n */\n\nimport type { CellClassParams, ColDef, ColGroupDef } from \"ag-grid-community\";\nimport {\n type DataFrame,\n type RowObjectType,\n type ValueDiffParams,\n type ValueDiffResult,\n} from \"../../../../api\";\n// Import directly from transforms to avoid circular dependency through barrel exports\nimport { dataFrameToRowObjects } from \"../../../../utils/transforms\";\nimport {\n createColumnNameRenderer,\n createPrimaryKeyIndicatorRenderer,\n renderMatchedPercentCell,\n} from \"../valueDiffCells\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for generating the value data grid\n */\nexport interface ValueDataGridOptions {\n /** Parameters from the value_diff run */\n params: ValueDiffParams;\n}\n\n/**\n * Result structure for the value data grid\n */\nexport interface ValueDataGridResult {\n columns: (ColDef<RowObjectType> | ColGroupDef<RowObjectType>)[];\n rows: RowObjectType[];\n}\n\n// ============================================================================\n// Column Schema Definition\n// ============================================================================\n\n/**\n * Schema definition for the value diff summary DataFrame\n *\n * This defines the expected structure of the data from the backend.\n * Using explicit schema avoids the \"basicColumns\" type fix pattern.\n */\nconst VALUE_DIFF_SUMMARY_SCHEMA: DataFrame[\"columns\"] = [\n { key: \"0\", name: \"Column\", type: \"text\" },\n { key: \"1\", name: \"Matched\", type: \"number\" },\n { key: \"2\", name: \"Matched %\", type: \"number\" },\n];\n\n// ============================================================================\n// Cell Class Functions\n// ============================================================================\n\n/**\n * Cell class function for matched columns\n * Applies \"diff-cell-modified\" class when match percentage is less than 100%\n */\nfunction getMatchedCellClass(\n params: CellClassParams<RowObjectType>,\n): string | undefined {\n const row = params.data;\n if (!row) return undefined;\n const value = row[\"2\"] as unknown as number | undefined;\n return value != null && value < 1 ? \"diff-cell-modified\" : undefined;\n}\n\n// ============================================================================\n// Comparators\n// ============================================================================\n\n/**\n * Custom comparator for the Matched % column that handles null/undefined values.\n * Null values are sorted last regardless of sort direction.\n */\nfunction matchedPercentComparator(\n a: number | null | undefined,\n b: number | null | undefined,\n): number {\n const aNull = a == null;\n const bNull = b == null;\n if (aNull && bNull) return 0;\n if (aNull) return 1;\n if (bNull) return -1;\n return a - b;\n}\n\n// ============================================================================\n// Column Definitions\n// ============================================================================\n\n/**\n * Column keys used in the value diff summary grid\n */\nconst COLUMN_KEYS = {\n PRIMARY_KEY_INDICATOR: \"__is_pk__\",\n COLUMN_NAME: \"0\",\n MATCHED_COUNT: \"1\",\n MATCHED_PERCENT: \"2\",\n} as const;\n\n/**\n * Creates column definitions for the value diff summary grid\n *\n * @param primaryKeys - Array of primary key column names\n * @param params - ValueDiffParams from the run\n * @returns Array of column definitions\n */\nfunction createColumnDefinitions(\n primaryKeys: string[],\n params: ValueDiffParams,\n): ColDef<RowObjectType>[] {\n return [\n // Primary key indicator column\n {\n field: COLUMN_KEYS.PRIMARY_KEY_INDICATOR,\n headerName: \"\",\n width: 30,\n maxWidth: 30,\n sortable: false,\n cellRenderer: createPrimaryKeyIndicatorRenderer(primaryKeys),\n },\n // Column name column with context menu\n {\n field: COLUMN_KEYS.COLUMN_NAME,\n headerName: \"Column\",\n resizable: true,\n cellRenderer: createColumnNameRenderer(params),\n cellClass: \"cell-show-context-menu\",\n },\n // Matched count column\n {\n field: COLUMN_KEYS.MATCHED_COUNT,\n headerName: \"Matched\",\n resizable: true,\n cellClass: getMatchedCellClass,\n },\n // Matched percentage column (default sort ascending — mismatches first)\n {\n field: COLUMN_KEYS.MATCHED_PERCENT,\n headerName: \"Matched %\",\n resizable: true,\n sort: \"asc\",\n comparator: matchedPercentComparator,\n cellRenderer: renderMatchedPercentCell,\n cellClass: getMatchedCellClass,\n },\n ];\n}\n\n// ============================================================================\n// Main Generator Function\n// ============================================================================\n\n/**\n * Generates grid configuration for value diff summary view\n *\n * @description Creates columns and rows for displaying column-level match\n * statistics. Each row represents a column from the compared data with\n * its match count and percentage.\n *\n * @param result - The value diff result containing summary data\n * @param options - Configuration options including params\n * @returns Grid columns and rows ready for ScreenshotDataGrid\n *\n * @example\n * const { columns, rows } = toValueDataGrid(result, {\n * params: run.params,\n * });\n */\nexport function toValueDataGrid(\n result: ValueDiffResult,\n options: ValueDataGridOptions,\n): ValueDataGridResult {\n const { params } = options;\n\n // Extract primary keys as array\n const primaryKeys = Array.isArray(params.primary_key)\n ? params.primary_key\n : [params.primary_key];\n\n // Apply schema to DataFrame for proper type handling\n // This ensures dataFrameToRowObjects has type information available\n const dataFrameWithSchema: DataFrame = {\n ...result.data,\n columns: VALUE_DIFF_SUMMARY_SCHEMA,\n };\n\n // Build columns using render functions from valueDiffCells\n const columns = createColumnDefinitions(primaryKeys, params);\n\n // Convert DataFrame to row objects\n const rows = dataFrameToRowObjects(dataFrameWithSchema);\n\n return { columns, rows };\n}\n","/**\n * @file dataGridFactory.ts\n * @description Abstract factory for creating data grid configurations\n *\n * This file provides a unified interface for generating grid data\n * for different run types (query, query_diff, value_diff, value_diff_detail, profile, profile_diff)\n *\n * It wraps the existing implementations:\n * - toDataGrid for single DataFrame display\n * - toDataDiffGrid for comparing two DataFrames\n * - toValueDiffGrid for joined diff data (with in_a/in_b columns)\n * - toValueDataGrid for value_diff summary (column match statistics)\n */\n\nimport Box from \"@mui/material/Box\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport type {\n ColDef,\n ColGroupDef,\n ICellRendererParams,\n} from \"ag-grid-community\";\nimport type { QueryDiffResult } from \"../../../api/adhocQuery\";\nimport type { NodeData } from \"../../../api/info\";\nimport type { ProfileDiffResult } from \"../../../api/profile\";\nimport type { RowCountDiffResult, RowCountResult } from \"../../../api/rowcount\";\nimport type { Run } from \"../../../api/types\";\nimport {\n type ColumnRenderMode,\n type ColumnType,\n type DataFrame,\n isProfileDiffRun,\n isProfileRun,\n isQueryBaseRun,\n isQueryDiffRun,\n isQueryRun,\n isRowCountDiffRun,\n isRowCountRun,\n isValueDiffDetailRun,\n isValueDiffRun,\n type RowObjectType,\n} from \"../../../api/types\";\nimport type { ValueDiffParams, ValueDiffResult } from \"../../../api/valuediff\";\nimport {\n mergeColumns,\n type SchemaDataGridOptions,\n type SchemaDataGridResult,\n type SingleEnvSchemaDataGridResult,\n toSchemaDataGrid,\n toSingleEnvDataGrid,\n} from \"../../../lib/dataGrid/generators/toSchemaDataGrid\";\n// Import existing implementations from @datarecce/ui\nimport {\n toDataDiffGridConfigured as toDataDiffGrid,\n toDataGridConfigured as toDataGrid,\n toRowCountDataGrid,\n toRowCountDiffDataGrid,\n toValueDiffGridConfigured as toValueDiffGrid,\n} from \"../../../utils/dataGrid\";\nimport { buildColumnTooltip, DataTypeIcon } from \"../DataTypeIcon\";\nimport { toValueDataGrid } from \"./generators/toValueDataGrid\";\n\n// ============================================================================\n// Types & Interfaces\n// ============================================================================\n\n/**\n * Common options shared across all grid types\n */\nexport interface BaseGridOptions {\n primaryKeys?: string[];\n pinnedColumns?: string[];\n columnsRenderMode?: Record<string, ColumnRenderMode>;\n onPrimaryKeyChange?: (primaryKeys: string[]) => void;\n onPinnedColumnsChange?: (pinnedColumns: string[]) => void;\n onColumnsRenderModeChanged?: (cols: Record<string, ColumnRenderMode>) => void;\n}\n\n/**\n * Additional options for diff grids\n */\nexport interface DiffGridOptions extends BaseGridOptions {\n changedOnly?: boolean;\n displayMode?: \"inline\" | \"side_by_side\";\n baseTitle?: string;\n currentTitle?: string;\n}\n\n/**\n * Standard output structure for all grid generation functions\n */\nexport interface DataGridResult {\n columns: ((ColDef<RowObjectType> | ColGroupDef<RowObjectType>) & {\n columnType?: ColumnType;\n columnRenderMode?: ColumnRenderMode;\n })[];\n rows: RowObjectType[];\n invalidPKeyBase?: boolean;\n invalidPKeyCurrent?: boolean;\n}\n\n/**\n * Discriminated union for run result data\n */\ntype RunResultData =\n | { kind: \"query\"; result: DataFrame }\n | { kind: \"query_diff_separate\"; result: QueryDiffResult }\n | {\n kind: \"query_diff_joined\";\n result: QueryDiffResult;\n primaryKeys: string[];\n }\n | { kind: \"value_diff\"; result: ValueDiffResult; params: ValueDiffParams }\n | { kind: \"value_diff_detail\"; result: DataFrame; primaryKeys: string[] }\n | { kind: \"profile\"; result: ProfileDiffResult }\n | { kind: \"profile_diff\"; result: ProfileDiffResult }\n // NEW: Add row count types\n | { kind: \"row_count\"; result: RowCountResult }\n | { kind: \"row_count_diff\"; result: RowCountDiffResult };\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Determines the appropriate grid generation strategy based on run type and data\n */\nfunction determineDataKind(run: Run): RunResultData | null {\n if (isQueryRun(run) || isQueryBaseRun(run)) {\n if (!run.result) return null;\n return { kind: \"query\", result: run.result };\n }\n\n if (isQueryDiffRun(run)) {\n if (!run.result) return null;\n // If the result has a `diff` field, it's pre-joined data\n if (run.result.diff && run.params?.primary_keys) {\n return {\n kind: \"query_diff_joined\",\n result: run.result,\n primaryKeys: run.params.primary_keys,\n };\n }\n // Otherwise, it's separate base/current DataFrames\n return { kind: \"query_diff_separate\", result: run.result };\n }\n\n if (isValueDiffRun(run)) {\n if (!run.result || !run.params) return null;\n return {\n kind: \"value_diff\",\n result: run.result,\n params: run.params,\n };\n }\n\n if (isValueDiffDetailRun(run)) {\n if (!run.result || !run.params?.primary_key) return null;\n const primaryKey = run.params.primary_key;\n const primaryKeys = Array.isArray(primaryKey) ? primaryKey : [primaryKey];\n return {\n kind: \"value_diff_detail\",\n result: run.result,\n primaryKeys,\n };\n }\n\n if (isProfileRun(run)) {\n if (!run.result?.current) return null;\n return { kind: \"profile\", result: run.result };\n }\n\n if (isProfileDiffRun(run)) {\n if (!run.result) return null;\n return { kind: \"profile_diff\", result: run.result };\n }\n\n if (isRowCountRun(run)) {\n if (!run.result) return null;\n return { kind: \"row_count\", result: run.result };\n }\n\n if (isRowCountDiffRun(run)) {\n if (!run.result) return null;\n return { kind: \"row_count_diff\", result: run.result };\n }\n\n return null;\n}\n\n/**\n * Cell renderer for column_name in single profile and side-by-side diff children.\n * Shows the column name + DataTypeIcon inline.\n */\nfunction profileColumnNameRenderer(params: ICellRendererParams<RowObjectType>) {\n const row = params.data;\n if (!row) return null;\n const name = params.value ? String(params.value) : \"\";\n const dataType = row.data_type ? String(row.data_type) : undefined;\n const tooltipText = buildColumnTooltip({ name, currentType: dataType });\n\n return (\n <Tooltip title={tooltipText} placement=\"top\">\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"6px\",\n overflow: \"hidden\",\n height: \"100%\",\n }}\n >\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {name}\n </Box>\n {dataType && <DataTypeIcon type={dataType} size={20} disableTooltip />}\n </Box>\n </Tooltip>\n );\n}\n\n/**\n * Cell renderer for column_name in inline diff mode.\n * Reads base/current data types from prefixed keys and renders DataTypeIcon(s).\n */\nfunction profileDiffColumnNameRenderer(\n params: ICellRendererParams<RowObjectType>,\n) {\n const row = params.data;\n if (!row) return null;\n const name = params.value ? String(params.value) : \"\";\n const baseType = row.base__data_type\n ? String(row.base__data_type)\n : undefined;\n const currentType = row.current__data_type\n ? String(row.current__data_type)\n : undefined;\n const isTypeChanged =\n baseType != null && currentType != null && baseType !== currentType;\n const displayType = currentType ?? baseType;\n const tooltipText = buildColumnTooltip({ name, baseType, currentType });\n\n return (\n <Tooltip title={tooltipText} placement=\"top\">\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"6px\",\n overflow: \"hidden\",\n height: \"100%\",\n }}\n >\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {name}\n </Box>\n {isTypeChanged ? (\n <Box\n component=\"span\"\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"2px\",\n }}\n >\n <Box\n component=\"span\"\n sx={{ textDecoration: \"line-through\", opacity: 0.6 }}\n >\n <DataTypeIcon type={baseType} size={20} disableTooltip />\n </Box>\n <Box component=\"span\" sx={{ fontSize: \"0.7em\", opacity: 0.5 }}>\n →\n </Box>\n <DataTypeIcon type={currentType} size={20} disableTooltip />\n </Box>\n ) : (\n displayType && (\n <DataTypeIcon type={displayType} size={20} disableTooltip />\n )\n )}\n </Box>\n </Tooltip>\n );\n}\n\n/**\n * Checks if a column field name represents a data_type column.\n * Matches \"data_type\", \"base__data_type\", and \"current__data_type\".\n */\nfunction isDataTypeField(field: string | undefined): boolean {\n if (!field) return false;\n const lower = field.toLowerCase();\n return (\n lower === \"data_type\" ||\n lower === \"base__data_type\" ||\n lower === \"current__data_type\"\n );\n}\n\n/**\n * Post-processes profile grid columns to:\n * 1. Remove data_type columns (flat and side-by-side children)\n * 2. Inject a custom cell renderer on column_name that shows name + DataTypeIcon inline\n */\nfunction injectProfileColumnNameRenderer(\n result: DataGridResult,\n): DataGridResult {\n const isInlineDiff =\n result.rows.length > 0 && Object.hasOwn(result.rows[0], \"base__data_type\");\n\n const columns = result.columns\n .filter((col) => {\n // Remove data_type columns from ColGroupDef children (side-by-side mode)\n if (\"children\" in col && col.children) {\n const filtered = col.children.filter(\n (child) => !isDataTypeField((child as ColDef<RowObjectType>).field),\n );\n if (filtered.length === 0) return false;\n (col as ColGroupDef<RowObjectType>).children = filtered;\n return true;\n }\n // Remove flat data_type columns\n return !isDataTypeField((col as ColDef<RowObjectType>).field);\n })\n .map((col) => {\n // Inject renderer on column_name columns\n if (\"children\" in col && col.children) {\n return {\n ...col,\n children: col.children.map((child) => {\n const childCol = child as ColDef<RowObjectType>;\n if (childCol.field?.toLowerCase() === \"column_name\") {\n return {\n ...childCol,\n cellRenderer: profileColumnNameRenderer,\n };\n }\n return child;\n }),\n };\n }\n const colDef = col as ColDef<RowObjectType>;\n if (colDef.field?.toLowerCase() === \"column_name\") {\n return {\n ...colDef,\n cellRenderer: isInlineDiff\n ? profileDiffColumnNameRenderer\n : profileColumnNameRenderer,\n };\n }\n return col;\n });\n\n return { ...result, columns };\n}\n\n/**\n * Extracts the primary key field name from profile data\n */\nfunction getProfilePrimaryKey(result: ProfileDiffResult): string {\n const field = result.current?.columns.find(\n (f) => f.name.toLowerCase() === \"column_name\",\n );\n return field?.name ?? \"column_name\";\n}\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\n/**\n * Creates grid data from a Run object and options\n *\n * This is the main entry point that abstracts away the different\n * data transformations needed for different run types.\n *\n * @param run - The Run object containing result data\n * @param options - Grid configuration options\n * @returns DataGridResult with columns and rows, or null if invalid\n *\n * @example\n * ```tsx\n * const gridData = createDataGrid(run, {\n * primaryKeys: ['id'],\n * pinnedColumns: ['name'],\n * changedOnly: true,\n * displayMode: 'inline',\n * });\n *\n * if (gridData) {\n * return <ScreenshotDataGrid columns={gridData.columns} rows={gridData.rows} />;\n * }\n * ```\n */\nexport function createDataGrid(\n run: Run,\n options: DiffGridOptions = {},\n): DataGridResult | null {\n const dataKind = determineDataKind(run);\n if (!dataKind) return null;\n\n switch (dataKind.kind) {\n case \"query\":\n return toDataGrid(dataKind.result, {\n primaryKeys: options.primaryKeys,\n pinnedColumns: options.pinnedColumns,\n columnsRenderMode: options.columnsRenderMode,\n onPrimaryKeyChange: options.onPrimaryKeyChange,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n });\n\n case \"query_diff_separate\":\n return toDataDiffGrid(\n dataKind.result.base,\n dataKind.result.current,\n options,\n );\n\n case \"query_diff_joined\":\n if (!dataKind.result.diff) {\n return null;\n }\n return toValueDiffGrid(dataKind.result.diff, dataKind.primaryKeys, {\n changedOnly: options.changedOnly,\n pinnedColumns: options.pinnedColumns,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n baseTitle: options.baseTitle,\n currentTitle: options.currentTitle,\n displayMode: options.displayMode,\n columnsRenderMode: options.columnsRenderMode,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n });\n\n case \"value_diff\":\n return toValueDataGrid(dataKind.result, { params: dataKind.params });\n\n case \"value_diff_detail\":\n return toValueDiffGrid(dataKind.result, dataKind.primaryKeys, {\n changedOnly: options.changedOnly,\n pinnedColumns: options.pinnedColumns,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n displayMode: options.displayMode,\n columnsRenderMode: options.columnsRenderMode,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n });\n\n case \"profile\": {\n if (!dataKind.result.current) {\n return null;\n }\n const primaryKey = getProfilePrimaryKey(dataKind.result);\n const profileResult = toDataGrid(dataKind.result.current, {\n primaryKeys: [primaryKey],\n pinnedColumns: options.pinnedColumns,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n columnsRenderMode: options.columnsRenderMode,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n });\n return injectProfileColumnNameRenderer(profileResult);\n }\n\n case \"profile_diff\": {\n const primaryKey = getProfilePrimaryKey(dataKind.result);\n const profileDiffResult = toDataDiffGrid(\n dataKind.result.base,\n dataKind.result.current,\n {\n primaryKeys: [primaryKey],\n pinnedColumns: options.pinnedColumns,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n displayMode: options.displayMode,\n columnsRenderMode: options.columnsRenderMode,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n },\n );\n return injectProfileColumnNameRenderer(profileDiffResult);\n }\n\n case \"row_count\":\n return toRowCountDataGrid(dataKind.result);\n\n case \"row_count_diff\":\n return toRowCountDiffDataGrid(dataKind.result);\n\n default:\n return null;\n }\n}\n\n/**\n * Input types for the data-only factory function\n */\nexport type DataGridInput =\n | { type: \"single\"; dataframe: DataFrame }\n | { type: \"dual\"; base?: DataFrame; current?: DataFrame }\n | { type: \"joined\"; dataframe: DataFrame; primaryKeys: string[] }\n | {\n type: \"schema_diff\";\n base?: NodeData[\"columns\"];\n current?: NodeData[\"columns\"];\n }\n | { type: \"schema_single\"; columns?: NodeData[\"columns\"] };\n\n/**\n * Union of all possible grid results from createDataGridFromData\n */\nexport type DataGridFromDataResult =\n | DataGridResult\n | SchemaDataGridResult\n | SingleEnvSchemaDataGridResult;\n\n/**\n * Alternative factory that accepts raw data instead of Run objects\n * Useful for testing or when you have data outside the Run structure\n *\n * @overload For DataFrame inputs, returns DataGridResult\n * @overload For schema inputs, returns schema-specific result types\n */\nexport function createDataGridFromData(\n input:\n | { type: \"joined\"; dataframe: DataFrame; primaryKeys: string[] }\n | { type: \"dual\"; base?: DataFrame; current?: DataFrame }\n | { type: \"single\"; dataframe: DataFrame },\n options?: DiffGridOptions,\n): DataGridResult;\nexport function createDataGridFromData(\n input: {\n type: \"schema_diff\";\n base?: NodeData[\"columns\"];\n current?: NodeData[\"columns\"];\n },\n options?: SchemaDataGridOptions,\n): SchemaDataGridResult;\nexport function createDataGridFromData(\n input: { type: \"schema_single\"; columns?: NodeData[\"columns\"] },\n options?: SchemaDataGridOptions,\n): SingleEnvSchemaDataGridResult;\nexport function createDataGridFromData(\n input: DataGridInput,\n options?: DiffGridOptions | SchemaDataGridOptions,\n): DataGridFromDataResult {\n switch (input.type) {\n case \"single\":\n return toDataGrid(input.dataframe, (options ?? {}) as DiffGridOptions);\n\n case \"dual\":\n return toDataDiffGrid(\n input.base,\n input.current,\n (options ?? {}) as DiffGridOptions,\n );\n\n case \"joined\":\n return toValueDiffGrid(\n input.dataframe,\n input.primaryKeys,\n (options ?? {}) as DiffGridOptions,\n );\n\n case \"schema_diff\": {\n const schemaDiff = mergeColumns(input.base, input.current);\n return toSchemaDataGrid(\n schemaDiff,\n (options ?? {}) as SchemaDataGridOptions,\n );\n }\n\n case \"schema_single\":\n return toSingleEnvDataGrid(\n input.columns,\n (options ?? {}) as SchemaDataGridOptions,\n );\n }\n}\n","\"use client\";\n\n/**\n * @file ProfileDiffForm.tsx\n * @description Form component for configuring profile diff parameters.\n *\n * This component allows users to:\n * - View the model being profiled\n * - Select specific columns to profile (or all columns)\n *\n * Uses the useModelColumns hook from @datarecce/ui/hooks to fetch column metadata.\n */\n\nimport Autocomplete from \"@mui/material/Autocomplete\";\nimport Box from \"@mui/material/Box\";\nimport Checkbox from \"@mui/material/Checkbox\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport Typography from \"@mui/material/Typography\";\nimport { useEffect, useState } from \"react\";\nimport { useModelColumns } from \"../../hooks\";\nimport type { RunFormProps } from \"../run\";\n\nexport interface ProfileDiffFormParams {\n model: string;\n primary_key?: string | (string | undefined)[];\n columns?: string[];\n}\n\ntype ProfileDiffFormProp = RunFormProps<ProfileDiffFormParams>;\n\nexport function ProfileDiffForm({\n params,\n onParamsChanged,\n setIsReadyToExecute,\n}: ProfileDiffFormProp) {\n const [allColumns, setAllColumns] = useState<boolean>(\n !params.columns || params.columns.length === 0,\n );\n\n const model = params.model;\n\n const { columns, isLoading, error } = useModelColumns(params.model);\n\n useEffect(() => {\n setIsReadyToExecute(!!model);\n }, [model, setIsReadyToExecute]);\n\n const columnNames = columns.map((c) => c.name);\n\n if (isLoading) {\n return <Box>Loading...</Box>;\n }\n\n if (columnNames.length === 0 || error) {\n return (\n <Box>\n Error: Please provide the 'catalog.json' to list column\n candidates\n </Box>\n );\n }\n\n return (\n <Stack spacing={5} sx={{ m: \"8px 24px\", pb: \"200px\" }}>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Model\n </Typography>\n <TextField\n fullWidth\n size=\"small\"\n value={params.model}\n slotProps={{ input: { readOnly: true } }}\n />\n </Box>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Columns\n </Typography>\n <FormControlLabel\n control={\n <Checkbox\n checked={allColumns}\n onChange={(e) => {\n setAllColumns(e.target.checked);\n onParamsChanged({\n ...params,\n columns: undefined,\n });\n }}\n size=\"small\"\n />\n }\n label=\"All columns\"\n sx={{ mb: \"10px\" }}\n />\n {!allColumns && (\n <Autocomplete\n multiple\n size=\"small\"\n disableCloseOnSelect\n options={columnNames}\n value={params.columns ?? []}\n onChange={(_, newValue) => {\n onParamsChanged({\n ...params,\n columns: newValue.length === 0 ? undefined : newValue,\n });\n }}\n renderInput={(inputProps) => (\n <TextField\n {...inputProps}\n placeholder={\n (params.columns ?? []).length === 0 ? \"Select columns\" : \"\"\n }\n className=\"no-track-pii-safe\"\n />\n )}\n />\n )}\n </Box>\n </Stack>\n );\n}\n","\"use client\";\n\n/**\n * @file ProfileResultView.tsx\n * @description Framework-agnostic Profile result view components for @datarecce/ui\n *\n * These components use the createResultView factory pattern and can be used by both\n * Recce OSS and Recce Cloud. They accept generic Run types and use type guards\n * for validation.\n *\n * The components display profile data in a grid format:\n * - ProfileResultView: Single environment profile stats (column metrics)\n * - ProfileDiffResultView: Diff between environments (base vs current column metrics)\n *\n * OSS-specific features (like RunToolbar, DiffDisplayModeSwitch) should be injected\n * via the header prop or by wrapping these components.\n */\n\nimport type { ReactNode } from \"react\";\nimport {\n type ColumnRenderMode,\n isProfileDiffRun,\n isProfileRun,\n type ProfileDiffResult,\n type ProfileDiffViewOptions,\n type Run,\n} from \"../../api\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps } from \"../result/types\";\nimport { RunToolbar } from \"../run/RunToolbar\";\nimport { DiffDisplayModeSwitch } from \"../ui/DiffDisplayModeSwitch\";\nimport { createDataGrid } from \"../ui/dataGrid/dataGridFactory\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with profile result (single environment)\n */\nexport type ProfileRun = Run & {\n type: \"profile\";\n result?: ProfileDiffResult;\n};\n\n/**\n * Run type with profile_diff result\n */\nexport type ProfileDiffRun = Run & {\n type: \"profile_diff\";\n result?: ProfileDiffResult;\n};\n\n/**\n * Props for ProfileResultView components\n */\nexport interface ProfileResultViewProps\n extends CreatedResultViewProps<ProfileDiffViewOptions> {\n run: ProfileRun | ProfileDiffRun | unknown;\n /**\n * Optional header element to render above the grid.\n * Use this to inject OSS-specific controls like RunToolbar.\n */\n header?: ReactNode;\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n// Type guard wrapper for factory compatibility (accepts unknown instead of Run)\ntype ProfileDiffRunInternal = Extract<\n Parameters<typeof isProfileDiffRun>[0],\n { type: \"profile_diff\" }\n>;\n\nconst isProfileDiffRunGuard = (run: unknown): run is ProfileDiffRunInternal =>\n typeof run === \"object\" &&\n run !== null &&\n \"type\" in run &&\n isProfileDiffRun(run as Parameters<typeof isProfileDiffRun>[0]);\n\ntype ProfileRunInternal = Extract<\n Parameters<typeof isProfileRun>[0],\n { type: \"profile\" }\n>;\n\nconst isProfileRunGuard = (run: unknown): run is ProfileRunInternal =>\n typeof run === \"object\" &&\n run !== null &&\n \"type\" in run &&\n isProfileRun(run as Parameters<typeof isProfileRun>[0]);\n\n// ============================================================================\n// Factory-Created Components\n// ============================================================================\n\n/**\n * Result view for single environment profile stats\n *\n * Displays a grid with column metrics (count, distinct, null proportion, etc.)\n * for a single dbt model.\n *\n * @example\n * ```tsx\n * <ProfileResultView run={profileRun} ref={gridRef} />\n * ```\n */\nexport const ProfileResultView = createResultView<\n ProfileRunInternal,\n ProfileDiffViewOptions\n>({\n displayName: \"ProfileResultView\",\n typeGuard: isProfileRunGuard,\n expectedRunType: \"profile\",\n screenshotWrapper: \"grid\",\n transformData: (run, { viewOptions, onViewOptionsChanged }) => {\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n\n // Default proportion columns to percentage display\n const columnsRenderMode = {\n distinct_proportion: \"percent\" as ColumnRenderMode,\n not_null_proportion: \"percent\" as ColumnRenderMode,\n ...viewOptions?.columnsRenderMode,\n };\n\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n const handlePinnedColumnsChanged = (newPinnedColumns: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: newPinnedColumns,\n });\n }\n };\n\n const gridData = createDataGrid(run, {\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n }) ?? { columns: [], rows: [] };\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n isEmpty: gridData.columns.length === 0,\n };\n },\n});\n\n/**\n * Result view for comparing profile stats between base and current environments\n *\n * Displays a grid with column metrics from both environments,\n * styled to highlight differences.\n *\n * @example\n * ```tsx\n * <ProfileDiffResultView\n * run={profileDiffRun}\n * ref={gridRef}\n * viewOptions={{ display_mode: \"inline\" }}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const ProfileDiffResultView = createResultView<\n ProfileDiffRunInternal,\n ProfileDiffViewOptions\n>({\n displayName: \"ProfileDiffResultView\",\n typeGuard: isProfileDiffRunGuard,\n expectedRunType: \"profile_diff\",\n screenshotWrapper: \"grid\",\n transformData: (run, { viewOptions, onViewOptionsChanged }) => {\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n const displayMode = viewOptions?.display_mode ?? \"inline\";\n\n // Default proportion columns to percentage display\n const columnsRenderMode = {\n distinct_proportion: \"percent\" as ColumnRenderMode,\n not_null_proportion: \"percent\" as ColumnRenderMode,\n ...viewOptions?.columnsRenderMode,\n };\n\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n const handlePinnedColumnsChanged = (newPinnedColumns: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: newPinnedColumns,\n });\n }\n };\n\n const gridData = createDataGrid(run, {\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n displayMode,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n }) ?? { columns: [], rows: [] };\n\n // OSS-specific header with RunToolbar and DiffDisplayModeSwitch\n const header = (\n <RunToolbar>\n <DiffDisplayModeSwitch\n displayMode={displayMode}\n onDisplayModeChanged={(mode) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n display_mode: mode,\n });\n }\n }}\n />\n </RunToolbar>\n );\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n header,\n isEmpty: gridData.columns.length === 0,\n };\n },\n});\n","\"use client\";\n\n/**\n * @file QueryDiffResultView.tsx\n * @description Framework-agnostic Query Diff result view for @datarecce/ui\n *\n * Handles both JOIN and non-JOIN modes:\n * - JOIN mode: Server computes the diff, result has `run.result.diff`\n * - Non-JOIN mode: Client-side diff, result has `run.result.base` and `run.result.current`\n *\n * Features:\n * - Displays row-level diff data with changed highlighting\n * - \"Changed only\" filter to show only differing rows\n * - Side-by-side vs inline display mode toggle\n * - Column pinning support\n * - Primary key selection (non-JOIN mode)\n * - Warning for truncated results\n * - Warning for non-unique primary keys (non-JOIN mode)\n */\n\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport type { Run } from \"../../api\";\nimport {\n type ColumnRenderMode,\n isQueryDiffRun,\n type QueryDiffViewOptions,\n type QueryPreviewChangeParams,\n} from \"../../api\";\nimport {\n toDataDiffGridConfigured,\n toValueDiffGridConfigured,\n} from \"../../utils\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\nimport { ChangedOnlyCheckbox } from \"../ui/ChangedOnlyCheckbox\";\nimport { DiffDisplayModeSwitch } from \"../ui/DiffDisplayModeSwitch\";\n\nimport \"./styles.css\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype QueryDiffRun = Extract<Run, { type: \"query_diff\" }>;\n\n/**\n * Props for QueryDiffResultView component\n */\nexport interface QueryDiffResultViewProps\n extends CreatedResultViewProps<QueryDiffViewOptions> {\n run: QueryDiffRun | unknown;\n}\n\n/**\n * Type guard wrapper that accepts unknown and delegates to typed guard.\n */\nfunction isQueryDiffRunGuard(run: unknown): run is QueryDiffRun {\n return isQueryDiffRun(run as Run);\n}\n\n/**\n * QueryDiffResultView component - displays query diff results in a data grid.\n *\n * Handles both JOIN and non-JOIN modes:\n * - JOIN mode: Server computes the diff, result has `run.result.diff`\n * - Non-JOIN mode: Client-side diff, result has `run.result.base` and `run.result.current`\n *\n * Key differences between modes:\n * - Primary key handling: only in non-JOIN mode (server handles it in JOIN mode)\n * - Warning sources: `diff.limit/more` vs `current.limit/more || base.more`\n * - \"No change\" empty state: only in JOIN mode with changedOnly=true\n *\n * Features:\n * - Displays row-level diff data with changed highlighting\n * - \"Changed only\" filter to show only differing rows\n * - Side-by-side vs inline display mode toggle\n * - Column pinning support\n * - Primary key selection (non-JOIN mode)\n * - Warning for truncated results\n * - Warning for non-unique primary keys (non-JOIN mode)\n *\n * @example\n * ```tsx\n * <QueryDiffResultView\n * run={queryDiffRun}\n * viewOptions={{ changed_only: true, display_mode: 'inline' }}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const QueryDiffResultView = createResultView<\n QueryDiffRun,\n QueryDiffViewOptions,\n DataGridHandle\n>({\n displayName: \"QueryDiffResultView\",\n typeGuard: isQueryDiffRunGuard,\n expectedRunType: \"query_diff\",\n screenshotWrapper: \"grid\",\n emptyState: \"No data\",\n transformData: (\n run,\n { viewOptions, onViewOptionsChanged },\n ): ResultViewData | null => {\n // Determine mode based on result structure\n const isJoinMode =\n run.result && \"diff\" in run.result && run.result.diff != null;\n\n // Compute baseTitle/currentTitle for sandbox editor\n let baseTitle: string | undefined;\n let currentTitle: string | undefined;\n if (run.params && (run.params as QueryPreviewChangeParams).current_model) {\n baseTitle = \"Original\";\n currentTitle = \"Editor\";\n }\n\n // Extract view options with defaults\n const changedOnly = viewOptions?.changed_only ?? false;\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n const displayMode = viewOptions?.display_mode ?? \"inline\";\n const columnsRenderMode = viewOptions?.columnsRenderMode ?? {};\n\n // Primary keys only used in non-JOIN mode\n const primaryKeys = !isJoinMode ? (viewOptions?.primary_keys ?? []) : [];\n\n // Create callbacks for view option changes\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n // Primary key handler only for non-JOIN mode\n const handlePrimaryKeyChanged = !isJoinMode\n ? (pks: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n primary_keys: pks,\n });\n }\n }\n : undefined;\n\n const handlePinnedColumnsChanged = (pinnedCols: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: pinnedCols,\n });\n }\n };\n\n // Build grid data using appropriate grid generator based on mode\n let gridData: {\n columns: unknown[];\n rows: unknown[];\n invalidPKeyBase?: boolean;\n invalidPKeyCurrent?: boolean;\n };\n\n if (isJoinMode && run.result?.diff) {\n // JOIN mode: use toValueDiffGrid with the diff data\n const primaryKeysFromParams = run.params?.primary_keys ?? [];\n gridData = toValueDiffGridConfigured(\n run.result.diff,\n primaryKeysFromParams,\n {\n changedOnly,\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n baseTitle,\n currentTitle,\n displayMode,\n },\n );\n } else {\n // Non-JOIN mode: use toDataDiffGrid with base/current data\n gridData = toDataDiffGridConfigured(\n run.result?.base,\n run.result?.current,\n {\n changedOnly,\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n baseTitle,\n currentTitle,\n displayMode,\n primaryKeys,\n onPrimaryKeyChange: handlePrimaryKeyChanged,\n },\n );\n }\n\n // Build warnings array\n const warnings: string[] = [];\n\n // Primary key uniqueness warning - only for non-JOIN mode\n if (!isJoinMode && primaryKeys.length > 0) {\n const pkName = primaryKeys.join(\", \");\n\n if (gridData.invalidPKeyBase && gridData.invalidPKeyCurrent) {\n warnings.push(\n `Warning: The primary key '${pkName}' is not unique in the base and current environments`,\n );\n } else if (gridData.invalidPKeyBase) {\n warnings.push(\n `Warning: The primary key '${pkName}' is not unique in the base environment`,\n );\n } else if (gridData.invalidPKeyCurrent) {\n warnings.push(\n `Warning: The primary key '${pkName}' is not unique in the current environment`,\n );\n }\n }\n\n // Limit warning - different sources for JOIN vs non-JOIN\n const limit = isJoinMode\n ? (run.result?.diff?.limit ?? 0)\n : (run.result?.current?.limit ?? 0);\n\n const hasMore = isJoinMode\n ? run.result?.diff?.more\n : run.result?.current?.more || run.result?.base?.more;\n\n if (limit > 0 && hasMore) {\n warnings.push(\n `Warning: Displayed results are limited to ${limit.toLocaleString()} records. To ensure complete data retrieval, consider applying a LIMIT or WHERE clause to constrain the result set.`,\n );\n }\n\n // Empty state when no columns (no data at all)\n if (gridData.columns.length === 0) {\n return { isEmpty: true };\n }\n\n // Build toolbar with display mode switch and changed only checkbox\n const toolbar = (\n <>\n <DiffDisplayModeSwitch\n displayMode={displayMode}\n onDisplayModeChanged={(newDisplayMode) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n display_mode: newDisplayMode,\n });\n }\n }}\n />\n <ChangedOnlyCheckbox\n changedOnly={viewOptions?.changed_only}\n onChange={() => {\n const newChangedOnly = !viewOptions?.changed_only;\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n changed_only: newChangedOnly,\n });\n }\n }}\n />\n </>\n );\n\n // \"No change\" empty state - only for JOIN mode with changedOnly=true\n if (isJoinMode && changedOnly && gridData.rows.length === 0) {\n return {\n isEmpty: true,\n emptyMessage: \"No change\",\n toolbar,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n warnings: warnings.length > 0 ? warnings : undefined,\n toolbar,\n defaultColumnOptions: {\n resizable: true,\n maxWidth: 800,\n minWidth: 35,\n },\n noRowsMessage: \"No mismatched rows\",\n };\n },\n}) as ForwardRefExoticComponent<\n QueryDiffResultViewProps & RefAttributes<DataGridHandle>\n>;\n\n// Re-export the view options type for convenience\nexport type { QueryDiffViewOptions };\n","\"use client\";\n\n/**\n * @file QueryResultView.tsx\n * @description Framework-agnostic Query result view for @datarecce/ui\n *\n * Displays query results in a data grid format. Uses the createResultView\n * factory pattern and can be used by both Recce OSS and Recce Cloud.\n *\n * Features:\n * - Displays query results with column pinning support\n * - Shows amber warning when results are truncated (limit exceeded)\n * - Optional \"Add to Checklist\" button in toolbar\n * - Supports both \"query\" and \"query_base\" run types\n */\n\nimport Button from \"@mui/material/Button\";\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport type { Run } from \"../../api\";\nimport {\n type ColumnRenderMode,\n isQueryBaseRun,\n isQueryRun,\n type QueryViewOptions,\n} from \"../../api\";\nimport { toDataGridConfigured } from \"../../utils\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype QueryRun = Extract<Run, { type: \"query\" }>;\ntype QueryBaseRun = Extract<Run, { type: \"query_base\" }>;\n\n/**\n * Props for QueryResultView component\n */\nexport interface QueryResultViewProps\n extends CreatedResultViewProps<QueryViewOptions> {\n run: QueryRun | QueryBaseRun | unknown;\n}\n\n/**\n * Type guard for query or query_base runs.\n * QueryResultView accepts both run types.\n * Wrapper accepts unknown and delegates to typed guards.\n */\nfunction isQueryOrQueryBaseRun(run: unknown): run is QueryRun | QueryBaseRun {\n return isQueryRun(run as Run) || isQueryBaseRun(run as Run);\n}\n\n/**\n * QueryResultView component - displays query results in a data grid.\n *\n * Features:\n * - Displays query results with column pinning support\n * - Shows amber warning when results are truncated (limit exceeded)\n * - Optional \"Add to Checklist\" button in toolbar\n * - Supports both \"query\" and \"query_base\" run types\n *\n * @example\n * ```tsx\n * <QueryResultView\n * run={queryRun}\n * viewOptions={{ pinned_columns: ['id'] }}\n * onViewOptionsChanged={setViewOptions}\n * onAddToChecklist={(run) => addToChecklist(run)}\n * />\n * ```\n */\nexport const QueryResultView = createResultView<\n QueryRun | QueryBaseRun,\n QueryViewOptions,\n DataGridHandle\n>({\n displayName: \"QueryResultView\",\n typeGuard: isQueryOrQueryBaseRun,\n expectedRunType: \"query\",\n screenshotWrapper: \"grid\",\n emptyState: \"No data\",\n transformData: (\n run,\n { viewOptions, onViewOptionsChanged, onAddToChecklist },\n ): ResultViewData | null => {\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n const columnsRenderMode = viewOptions?.columnsRenderMode ?? {};\n\n // Create callbacks for view option changes\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n const handlePinnedColumnsChanged = (pinnedColumns: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: pinnedColumns,\n });\n }\n };\n\n // Build grid data using toDataGridConfigured\n if (!run.result) {\n return { isEmpty: true };\n }\n\n const gridData = toDataGridConfigured(run.result, {\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n });\n\n // Empty state when no columns\n if (gridData.columns.length === 0) {\n return { isEmpty: true };\n }\n\n // Build warnings array\n const dataframe = run.result;\n const limit = dataframe ? (dataframe.limit ?? 0) : 0;\n const warnings: string[] = [];\n if (limit > 0 && dataframe?.more) {\n warnings.push(\n `Warning: Displayed results are limited to ${limit.toLocaleString()} records. To ensure complete data retrieval, consider applying a LIMIT or WHERE clause to constrain the result set.`,\n );\n }\n\n // Build toolbar with \"Add to Checklist\" button\n const toolbar = onAddToChecklist ? (\n <Button\n sx={{ my: \"5px\" }}\n size=\"small\"\n color=\"iochmara\"\n variant=\"contained\"\n onClick={() => {\n onAddToChecklist(run);\n }}\n >\n Add to Checklist\n </Button>\n ) : undefined;\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n warnings: warnings.length > 0 ? warnings : undefined,\n warningStyle: \"amber\",\n toolbar,\n defaultColumnOptions: {\n resizable: true,\n maxWidth: 800,\n minWidth: 35,\n },\n };\n },\n}) as ForwardRefExoticComponent<\n QueryResultViewProps & RefAttributes<DataGridHandle>\n>;\n\n// Re-export the view options type for convenience\nexport type { QueryViewOptions };\n","\"use client\";\n\n/**\n * @file RowCountResultView.tsx\n * @description Framework-agnostic Row Count result view components for @datarecce/ui\n *\n * These components use the createResultView factory pattern and can be used by both\n * Recce OSS and Recce Cloud. They accept generic Run types and use type guards\n * for validation.\n *\n * The components display row count data in a grid format:\n * - RowCountResultView: Single environment row counts (name, count)\n * - RowCountDiffResultView: Diff between environments (name, base, current, delta)\n */\n\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n isRowCountDiffRun,\n isRowCountRun,\n type RowCountDiffResult,\n type RowCountResult,\n type Run,\n} from \"../../api\";\nimport { toRowCountDataGrid, toRowCountDiffDataGrid } from \"../../utils\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with row_count result\n */\nexport type RowCountRun = Run & {\n type: \"row_count\";\n result?: RowCountResult;\n};\n\n/**\n * Run type with row_count_diff result\n */\nexport type RowCountDiffRun = Run & {\n type: \"row_count_diff\";\n result?: RowCountDiffResult;\n};\n\n/**\n * Props for RowCountResultView components\n */\nexport interface RowCountResultViewProps\n extends CreatedResultViewProps<unknown> {\n run: RowCountRun | RowCountDiffRun | unknown;\n}\n\n// ============================================================================\n// Type Guards (wrapper to accept unknown)\n// ============================================================================\n\nfunction isRowCountRunGuard(run: unknown): run is RowCountRun {\n return isRowCountRun(run as Run);\n}\n\nfunction isRowCountDiffRunGuard(run: unknown): run is RowCountDiffRun {\n return isRowCountDiffRun(run as Run);\n}\n\n// ============================================================================\n// Transform Functions\n// ============================================================================\n\n/**\n * Transform RowCountRun data to grid format\n */\nfunction transformRowCountData(run: RowCountRun): ResultViewData | null {\n if (!run.result) {\n return null;\n }\n\n const gridData = toRowCountDataGrid(run.result);\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n isEmpty: gridData.rows.length === 0,\n };\n}\n\n/**\n * Transform RowCountDiffRun data to grid format\n */\nfunction transformRowCountDiffData(\n run: RowCountDiffRun,\n): ResultViewData | null {\n if (!run.result) {\n return null;\n }\n\n const gridData = toRowCountDiffDataGrid(run.result);\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n isEmpty: gridData.rows.length === 0,\n };\n}\n\n// ============================================================================\n// Factory-Created Components\n// ============================================================================\n\n/**\n * Result view for single environment row counts\n *\n * Displays a grid with model names and their row counts.\n *\n * @example\n * ```tsx\n * <RowCountResultView run={rowCountRun} ref={gridRef} />\n * ```\n */\nexport const RowCountResultView = createResultView<\n RowCountRun,\n unknown,\n DataGridHandle\n>({\n displayName: \"RowCountResultView\",\n typeGuard: isRowCountRunGuard,\n expectedRunType: \"row_count\",\n screenshotWrapper: \"grid\",\n transformData: transformRowCountData,\n emptyState: \"No nodes matched\",\n}) as ForwardRefExoticComponent<\n RowCountResultViewProps & RefAttributes<DataGridHandle>\n>;\n\n/**\n * Result view for comparing row counts between base and current environments\n *\n * Displays a grid with model names, base counts, current counts, and delta.\n * Cells are styled to indicate added (green) or removed (red) rows.\n *\n * @example\n * ```tsx\n * <RowCountDiffResultView run={rowCountDiffRun} ref={gridRef} />\n * ```\n */\nexport const RowCountDiffResultView = createResultView<\n RowCountDiffRun,\n unknown,\n DataGridHandle\n>({\n displayName: \"RowCountDiffResultView\",\n typeGuard: isRowCountDiffRunGuard,\n expectedRunType: \"row_count_diff\",\n screenshotWrapper: \"grid\",\n transformData: transformRowCountDiffData,\n emptyState: \"No nodes matched\",\n}) as ForwardRefExoticComponent<\n RowCountResultViewProps & RefAttributes<DataGridHandle>\n>;\n","\"use client\";\n\n/**\n * @file TopKDiffForm.tsx\n * @description Form component for Top-K diff parameters.\n *\n * This component allows users to select a column for top-K analysis.\n * It displays a dropdown of available columns from the model and\n * requires catalog.json to be available for column listing.\n */\n\nimport Box from \"@mui/material/Box\";\nimport FormControl from \"@mui/material/FormControl\";\nimport FormLabel from \"@mui/material/FormLabel\";\nimport NativeSelect from \"@mui/material/NativeSelect\";\nimport { useEffect } from \"react\";\nimport type { TopKDiffParams } from \"../../api\";\nimport { useModelColumns } from \"../../hooks\";\nimport type { RunFormProps } from \"../run\";\n\ntype TopKDiffFormProps = RunFormProps<TopKDiffParams>;\n\nexport function TopKDiffForm({\n params,\n onParamsChanged,\n setIsReadyToExecute,\n}: TopKDiffFormProps) {\n const { columns, isLoading, error } = useModelColumns(params.model);\n const columnNames = columns.map((c) => c.name);\n\n useEffect(() => {\n setIsReadyToExecute(!!params.column_name);\n }, [params, setIsReadyToExecute]);\n\n if (isLoading) {\n return <Box>Loading...</Box>;\n }\n\n if (columnNames.length === 0 || error) {\n return (\n <Box>\n Error: Please provide the 'catalog.json' to list column\n candidates\n </Box>\n );\n }\n\n return (\n <Box sx={{ m: \"16px\" }}>\n <FormControl fullWidth>\n <FormLabel sx={{ mb: 1 }}>Pick a column to show top-k</FormLabel>\n <NativeSelect\n value={params.column_name}\n onChange={(e) => {\n const column = e.target.value;\n onParamsChanged({ ...params, column_name: column });\n }}\n >\n <option value=\"\">Select column</option>\n {columnNames.map((c) => (\n <option key={c} value={c} className=\"no-track-pii-safe\">\n {c}\n </option>\n ))}\n </NativeSelect>\n </FormControl>\n </Box>\n );\n}\n","\"use client\";\n\n/**\n * @file TopKDiffResultView.tsx\n * @description Framework-agnostic Top-K diff result view component for @datarecce/ui\n *\n * This component uses the createResultView factory pattern and can be used by both\n * Recce OSS and Recce Cloud. It accepts generic Run types and uses type guards\n * for validation.\n *\n * The component displays top-K value distribution comparison:\n * - Horizontal bar chart comparing base vs current top-K values\n * - \"View More Items\" / \"View Only Top-10\" toggle when >10 items exist\n * - Title shows model name and column name\n * - Dark/light theme support\n */\n\nimport Box from \"@mui/material/Box\";\nimport Link from \"@mui/material/Link\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n isTopKDiffRun,\n type Run,\n type TopKDiffParams,\n type TopKDiffResult,\n type TopKViewOptions,\n} from \"../../api\";\nimport { useIsDark } from \"../../hooks\";\nimport { TopKBarChart } from \"../data/TopKBarChart\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with top_k_diff result\n */\nexport type TopKDiffRun = Run & {\n type: \"top_k_diff\";\n params?: TopKDiffParams;\n result?: TopKDiffResult;\n};\n\n/**\n * Props for TopKDiffResultView component\n */\nexport interface TopKDiffResultViewProps\n extends CreatedResultViewProps<TopKViewOptions> {\n run: TopKDiffRun | unknown;\n}\n\n// ============================================================================\n// Type Guard (wrapper to accept unknown)\n// ============================================================================\n\n/**\n * Type guard wrapper that accepts unknown and delegates to typed guard.\n */\nfunction isTopKDiffRunGuard(run: unknown): run is TopKDiffRun {\n return isTopKDiffRun(run as Run);\n}\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\n/**\n * Title component for the top-K chart.\n * Uses useIsDark hook for theme-aware styling.\n */\nfunction TopKTitle({\n model,\n columnName,\n}: {\n model: string;\n columnName: string;\n}) {\n const isDark = useIsDark();\n return (\n <Typography\n variant=\"h5\"\n sx={{\n pt: 4,\n textAlign: \"center\",\n color: isDark ? \"grey.200\" : \"grey.600\",\n }}\n >\n Model {model}.{columnName}\n </Typography>\n );\n}\n\n/**\n * View toggle link for switching between top-10 and all items.\n */\nfunction ViewToggleLink({\n showAll,\n onToggle,\n}: {\n showAll: boolean;\n onToggle: () => void;\n}) {\n return (\n <Box sx={{ display: \"flex\", p: 5, justifyContent: \"start\" }}>\n <Link\n component=\"button\"\n onClick={onToggle}\n sx={{ color: \"iochmara.main\", cursor: \"pointer\" }}\n >\n {showAll ? \"View Only Top-10\" : \"View More Items\"}\n </Link>\n </Box>\n );\n}\n\n// ============================================================================\n// Transform Function\n// ============================================================================\n\n/**\n * Transform TopKDiffRun data to result view format\n */\nfunction transformTopKDiffData(\n run: TopKDiffRun,\n {\n viewOptions,\n onViewOptionsChanged,\n }: {\n viewOptions?: TopKViewOptions;\n onViewOptionsChanged?: (options: TopKViewOptions) => void;\n },\n): ResultViewData | null {\n const result = run.result;\n const params = run.params as TopKDiffParams;\n\n // Empty state when no result\n if (!result) {\n return { isEmpty: true };\n }\n\n const baseTopK = result.base;\n const currentTopK = result.current;\n\n // Derive isDisplayTopTen from viewOptions (inverted: show_all=false means top10=true)\n const showAll = viewOptions?.show_all ?? false;\n const isDisplayTopTen = !showAll;\n\n // Check if toggle should be visible (>10 items in either base or current)\n const shouldShowToggle =\n baseTopK.values.length > 10 || currentTopK.values.length > 10;\n\n // Build footer with toggle link (if needed)\n const footer = shouldShowToggle ? (\n <ViewToggleLink\n showAll={showAll}\n onToggle={() => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n show_all: !showAll,\n });\n }\n }}\n />\n ) : undefined;\n\n return {\n content: (\n <>\n <TopKTitle model={params.model} columnName={params.column_name} />\n <Stack direction=\"row\" alignItems=\"center\">\n <Box sx={{ flex: 1 }} />\n <TopKBarChart\n baseData={baseTopK}\n currentData={currentTopK}\n showComparison={true}\n maxItems={isDisplayTopTen ? 10 : undefined}\n />\n <Box sx={{ flex: 1 }} />\n </Stack>\n </>\n ),\n footer,\n };\n}\n\n// ============================================================================\n// Factory-Created Component\n// ============================================================================\n\n/**\n * TopKDiffResultView component - displays top-K value distribution comparison.\n *\n * Features:\n * - Displays horizontal bar chart comparing base vs current top-K values\n * - \"View More Items\" / \"View Only Top-10\" toggle when >10 items exist\n * - Title shows model name and column name\n * - Dark/light theme support\n *\n * @example\n * ```tsx\n * <TopKDiffResultView\n * run={topKDiffRun}\n * viewOptions={{ show_all: false }}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const TopKDiffResultView = createResultView<\n TopKDiffRun,\n TopKViewOptions,\n HTMLDivElement\n>({\n displayName: \"TopKDiffResultView\",\n typeGuard: isTopKDiffRunGuard,\n expectedRunType: \"top_k_diff\",\n screenshotWrapper: \"box\",\n emptyState: \"No data\",\n transformData: transformTopKDiffData,\n}) as ForwardRefExoticComponent<\n TopKDiffResultViewProps & RefAttributes<HTMLDivElement>\n>;\n","\"use client\";\n\n/**\n * @file ValueDiffDetailResultView.tsx\n * @description Framework-agnostic Value Diff Detail result view for @datarecce/ui\n *\n * Displays row-level value diff data in a data grid format. Uses the createResultView\n * factory pattern and can be used by both Recce OSS and Recce Cloud.\n *\n * Features:\n * - Displays row-level diff data with changed highlighting\n * - \"Changed only\" filter to show only differing rows\n * - Side-by-side vs inline display mode toggle\n * - Column pinning support\n * - Shows amber warning when results are truncated\n * - Toolbar-in-empty-state pattern: shows \"No change\" when changed_only=true but no changes\n */\n\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n type ColumnRenderMode,\n isValueDiffDetailRun,\n type Run,\n type ValueDiffDetailViewOptions,\n} from \"../../api\";\nimport { toValueDiffGridConfigured } from \"../../utils\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\nimport { ChangedOnlyCheckbox } from \"../ui/ChangedOnlyCheckbox\";\nimport { DiffDisplayModeSwitch } from \"../ui/DiffDisplayModeSwitch\";\n\n// Import AG Grid styles for context menu visibility\nimport \"../data/agGridStyles.css\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with value_diff_detail result\n */\nexport type ValueDiffDetailRun = Run & {\n type: \"value_diff_detail\";\n};\n\n/**\n * Props for ValueDiffDetailResultView component\n */\nexport interface ValueDiffDetailResultViewProps\n extends CreatedResultViewProps<ValueDiffDetailViewOptions> {\n run: ValueDiffDetailRun | unknown;\n}\n\n// ============================================================================\n// Type Guard\n// ============================================================================\n\n/**\n * Type guard wrapper that accepts unknown and delegates to typed guard.\n */\nfunction isValueDiffDetailRunGuard(run: unknown): run is ValueDiffDetailRun {\n return isValueDiffDetailRun(run as Run);\n}\n\n// ============================================================================\n// Factory-Created Component\n// ============================================================================\n\n/**\n * ValueDiffDetailResultView component - displays value diff details in a data grid.\n *\n * Features:\n * - Displays row-level diff data with changed highlighting\n * - \"Changed only\" filter to show only differing rows\n * - Side-by-side vs inline display mode toggle\n * - Column pinning support\n * - Shows amber warning when results are truncated\n * - Toolbar-in-empty-state pattern: shows \"No change\" when changed_only=true but no changes\n *\n * @example\n * ```tsx\n * <ValueDiffDetailResultView\n * run={valueDiffDetailRun}\n * viewOptions={{ changed_only: true, display_mode: 'inline' }}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const ValueDiffDetailResultView = createResultView<\n ValueDiffDetailRun,\n ValueDiffDetailViewOptions,\n DataGridHandle\n>({\n displayName: \"ValueDiffDetailResultView\",\n typeGuard: isValueDiffDetailRunGuard,\n expectedRunType: \"value_diff_detail\",\n screenshotWrapper: \"grid\",\n emptyState: \"No data\",\n transformData: (\n run,\n { viewOptions, onViewOptionsChanged },\n ): ResultViewData | null => {\n const changedOnly = viewOptions?.changed_only ?? false;\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n const displayMode = viewOptions?.display_mode ?? \"inline\";\n const columnsRenderMode = viewOptions?.columnsRenderMode ?? {};\n\n // Extract primary keys from params\n const primaryKey = run.params?.primary_key;\n if (!primaryKey || !run.result) {\n return { isEmpty: true };\n }\n const primaryKeys = Array.isArray(primaryKey) ? primaryKey : [primaryKey];\n\n // Create callbacks for view option changes\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n const handlePinnedColumnsChanged = (pinnedCols: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: pinnedCols,\n });\n }\n };\n\n // Build grid data using toValueDiffGridConfigured\n const gridData = toValueDiffGridConfigured(run.result, primaryKeys, {\n changedOnly,\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n displayMode,\n });\n\n // Empty state when no columns (no data at all)\n if (gridData.columns.length === 0) {\n return { isEmpty: true };\n }\n\n // Build warnings array\n const limit = run.result?.limit ?? 0;\n const warnings: string[] = [];\n if (limit > 0 && run.result?.more) {\n warnings.push(\n `Warning: Displayed results are limited to ${limit.toLocaleString()} records. To ensure complete data retrieval, consider applying a LIMIT or WHERE clause to constrain the result set.`,\n );\n }\n\n // Build toolbar with display mode switch and changed only checkbox\n const toolbar = (\n <>\n <DiffDisplayModeSwitch\n displayMode={displayMode}\n onDisplayModeChanged={(newDisplayMode) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n display_mode: newDisplayMode,\n });\n }\n }}\n />\n <ChangedOnlyCheckbox\n changedOnly={viewOptions?.changed_only}\n onChange={() => {\n const newChangedOnly = !viewOptions?.changed_only;\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n changed_only: newChangedOnly,\n });\n }\n }}\n />\n </>\n );\n\n // Toolbar-in-empty-state pattern: when changed_only is true but no changed rows\n if (changedOnly && gridData.rows.length === 0) {\n return {\n isEmpty: true,\n emptyMessage: \"No change\",\n toolbar,\n warnings: warnings.length > 0 ? warnings : undefined,\n warningStyle: \"amber\",\n };\n }\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n warnings: warnings.length > 0 ? warnings : undefined,\n warningStyle: \"amber\",\n toolbar,\n defaultColumnOptions: {\n resizable: true,\n maxWidth: 800,\n minWidth: 35,\n },\n noRowsMessage: \"No mismatched rows\",\n };\n },\n}) as ForwardRefExoticComponent<\n ValueDiffDetailResultViewProps & RefAttributes<DataGridHandle>\n>;\n\n// Re-export the view options type for convenience\nexport type { ValueDiffDetailViewOptions };\n","\"use client\";\n\n/**\n * @file ValueDiffForm.tsx\n * @description Form component for configuring value diff parameters.\n *\n * This component allows users to:\n * - View the model being compared\n * - Select primary keys for joining records\n * - Select specific columns to compare (or all columns)\n *\n * Uses the useModelColumns hook from @datarecce/ui/hooks to fetch column metadata.\n */\n\nimport Autocomplete from \"@mui/material/Autocomplete\";\nimport Box from \"@mui/material/Box\";\nimport Checkbox from \"@mui/material/Checkbox\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport Typography from \"@mui/material/Typography\";\nimport { useEffect, useState } from \"react\";\nimport { useModelColumns } from \"../../hooks\";\nimport type { RunFormProps } from \"../run\";\n\nexport interface ValueDiffFormParams {\n model: string;\n primary_key?: string | (string | undefined)[];\n columns?: string[];\n}\n\ntype ValueDiffFormProp = RunFormProps<ValueDiffFormParams>;\n\nexport function ValueDiffForm({\n params,\n onParamsChanged,\n setIsReadyToExecute,\n}: ValueDiffFormProp) {\n const [allColumns, setAllColumns] = useState<boolean>(\n !params.columns || params.columns.length === 0,\n );\n\n const model = params.model;\n const primaryKey = params.primary_key;\n\n const {\n columns,\n primaryKey: nodePrimaryKey,\n isLoading,\n error,\n } = useModelColumns(params.model);\n\n useEffect(() => {\n if (!primaryKey && nodePrimaryKey) {\n onParamsChanged({\n ...params,\n primary_key: nodePrimaryKey,\n });\n }\n }, [primaryKey, nodePrimaryKey, params, onParamsChanged]);\n\n useEffect(() => {\n setIsReadyToExecute(!!(primaryKey && model));\n }, [primaryKey, model, setIsReadyToExecute]);\n\n const columnNames = columns.map((c) => c.name);\n\n // primaryKey can be an array or string, map to array\n const primaryKeys = Array.isArray(primaryKey)\n ? primaryKey\n : primaryKey\n ? [primaryKey]\n : undefined;\n\n if (isLoading) {\n return <Box>Loading...</Box>;\n }\n\n if (columnNames.length === 0 || error) {\n return (\n <Box>\n Error: Please provide the 'catalog.json' to list column\n candidates\n </Box>\n );\n }\n\n return (\n <Stack spacing={5} sx={{ m: \"8px 24px\", pb: \"200px\" }}>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Model\n </Typography>\n <TextField\n fullWidth\n size=\"small\"\n value={params.model}\n slotProps={{ input: { readOnly: true } }}\n />\n </Box>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Primary key\n </Typography>\n <Autocomplete\n multiple\n size=\"small\"\n disableCloseOnSelect\n options={columnNames}\n value={(primaryKeys ?? []).filter(\n (c): c is string => c !== undefined,\n )}\n onChange={(_, newValue) => {\n onParamsChanged({\n ...params,\n primary_key: newValue.length === 1 ? newValue[0] : newValue,\n });\n }}\n renderInput={(inputProps) => (\n <TextField\n {...inputProps}\n placeholder={\n (primaryKeys ?? []).length === 0 ? \"Select primary key\" : \"\"\n }\n className=\"no-track-pii-safe\"\n />\n )}\n />\n </Box>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Columns\n </Typography>\n <FormControlLabel\n control={\n <Checkbox\n checked={allColumns}\n onChange={(e) => {\n setAllColumns(e.target.checked);\n onParamsChanged({\n ...params,\n columns: undefined,\n });\n }}\n size=\"small\"\n />\n }\n label=\"All columns\"\n sx={{ mb: \"10px\" }}\n />\n {!allColumns && (\n <Autocomplete\n multiple\n size=\"small\"\n disableCloseOnSelect\n options={columnNames}\n value={params.columns ?? []}\n onChange={(_, newValue) => {\n onParamsChanged({\n ...params,\n columns: newValue.length === 0 ? undefined : newValue,\n });\n }}\n renderInput={(inputProps) => (\n <TextField\n {...inputProps}\n placeholder={\n (params.columns ?? []).length === 0 ? \"Select columns\" : \"\"\n }\n className=\"no-track-pii-safe\"\n />\n )}\n />\n )}\n </Box>\n </Stack>\n );\n}\n","\"use client\";\n\n/**\n * @file ValueDiffResultView.tsx\n * @description Framework-agnostic Value Diff summary result view for @datarecce/ui\n *\n * Displays column-level match statistics from a value_diff run in a data grid format.\n * Each row represents a column with its match count and percentage.\n *\n * Uses the createResultView factory pattern and can be used by both Recce OSS and Recce Cloud.\n */\n\nimport Box from \"@mui/material/Box\";\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n isValueDiffRun,\n type Run,\n type ValueDiffParams,\n type ValueDiffResult,\n} from \"../../api\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\nimport { toValueDataGrid } from \"../ui/dataGrid\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with value_diff result\n */\nexport type ValueDiffRun = Run & {\n type: \"value_diff\";\n result?: ValueDiffResult;\n params?: ValueDiffParams;\n};\n\n/**\n * Props for ValueDiffResultView component\n */\nexport interface ValueDiffResultViewProps\n extends CreatedResultViewProps<unknown> {\n run: ValueDiffRun | unknown;\n}\n\n// ============================================================================\n// Type Guard\n// ============================================================================\n\n/**\n * Type guard wrapper that accepts unknown and delegates to typed guard.\n */\nfunction isValueDiffRunGuard(run: unknown): run is ValueDiffRun {\n return isValueDiffRun(run as Run);\n}\n\n// ============================================================================\n// Header Component\n// ============================================================================\n\ninterface SummaryHeaderProps {\n params: ValueDiffParams;\n summary: ValueDiffResult[\"summary\"];\n}\n\nfunction SummaryHeader({ params, summary }: SummaryHeaderProps) {\n const common = summary.total - summary.added - summary.removed;\n\n return (\n <Box sx={{ px: \"16px\", pt: \"5px\", pb: \"5px\" }}>\n Model: {params.model}, {summary.total} total ({common} common,{\" \"}\n {summary.added} added, {summary.removed} removed)\n </Box>\n );\n}\n\n// ============================================================================\n// Transform Function\n// ============================================================================\n\nfunction transformValueDiffData(run: ValueDiffRun): ResultViewData | null {\n if (!run.result || !run.params) {\n return { renderNull: true };\n }\n\n const gridData = toValueDataGrid(run.result, { params: run.params });\n\n if (!gridData) {\n return { renderNull: true };\n }\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n isEmpty: false,\n header: <SummaryHeader params={run.params} summary={run.result.summary} />,\n };\n}\n\n// ============================================================================\n// Factory-Created Component\n// ============================================================================\n\n/**\n * ValueDiffResultView component - displays value diff summary in a data grid.\n *\n * Features:\n * - Displays column-level match statistics\n * - Summary header with model name and row counts (total, common, added, removed)\n * - Highlights columns with match percentage < 100%\n *\n * @example\n * ```tsx\n * <ValueDiffResultView run={valueDiffRun} />\n * ```\n */\nexport const ValueDiffResultView = createResultView<\n ValueDiffRun,\n unknown,\n DataGridHandle\n>({\n displayName: \"ValueDiffResultView\",\n typeGuard: isValueDiffRunGuard,\n expectedRunType: \"value_diff\",\n screenshotWrapper: \"grid\",\n transformData: transformValueDiffData,\n}) as ForwardRefExoticComponent<\n ValueDiffResultViewProps & RefAttributes<DataGridHandle>\n>;\n","\"use client\";\n\n/**\n * @file run/registry.ts\n * @description Run type registry with icons and components.\n *\n * This module provides:\n * - `RegistryEntry` interface for typed registry entries\n * - `RunRegistry` interface for the full registry mapping\n * - `registry` const with all run type configurations\n * - `findByRunType()` helper for looking up registry entries\n *\n * All run type icons are from react-icons. Components available in\n * @datarecce/ui are included; others are undefined and can be extended.\n */\n\nimport { LuChartBarBig } from \"react-icons/lu\";\nimport { MdFormatListNumberedRtl, MdSchema } from \"react-icons/md\";\nimport {\n TbAlignBoxLeftStretch,\n TbBrandStackshare,\n TbChartHistogram,\n TbEyeEdit,\n TbEyeSearch,\n TbSql,\n} from \"react-icons/tb\";\nimport type { RunType } from \"../../api\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { HistogramDiffForm } from \"../histogram/HistogramDiffForm\";\nimport { HistogramDiffResultView } from \"../histogram/HistogramResultView\";\nimport { ProfileDiffForm } from \"../profile/ProfileDiffForm\";\nimport {\n ProfileDiffResultView,\n ProfileResultView,\n} from \"../profile/ProfileResultView\";\nimport { QueryDiffResultView } from \"../query/QueryDiffResultView\";\nimport { QueryResultView } from \"../query/QueryResultView\";\nimport {\n RowCountDiffResultView,\n RowCountResultView,\n} from \"../rowcount/RowCountResultView\";\nimport { TopKDiffForm } from \"../top-k/TopKDiffForm\";\nimport { TopKDiffResultView } from \"../top-k/TopKDiffResultView\";\nimport { ValueDiffDetailResultView } from \"../valuediff/ValueDiffDetailResultView\";\nimport { ValueDiffForm } from \"../valuediff/ValueDiffForm\";\nimport { ValueDiffResultView } from \"../valuediff/ValueDiffResultView\";\nimport type {\n RefTypes,\n RegistryEntry,\n RunFormParamTypes,\n RunTypeConfig,\n ViewOptionTypes,\n} from \"./types\";\n\n// ============================================================================\n// Re-export types for consumers\n// ============================================================================\n\nexport type {\n IconComponent,\n PartialRunTypeRegistry,\n RefTypes,\n RegistryEntry,\n RunFormParamTypes,\n RunFormProps,\n RunResultViewProps,\n RunResultViewRef,\n RunTypeConfig,\n RunTypeRegistry,\n ViewOptionTypes,\n} from \"./types\";\n\n// ============================================================================\n// Run Registry Interface\n// ============================================================================\n\n/**\n * Interface for the run registry with specific component types for each run type.\n * This provides precise typing for each entry's ref type and view options.\n */\nexport interface RunRegistry {\n query: RegistryEntry<DataGridHandle>;\n query_base: RegistryEntry<DataGridHandle>;\n query_diff: RegistryEntry<DataGridHandle>;\n row_count: RegistryEntry<DataGridHandle>;\n row_count_diff: RegistryEntry<DataGridHandle>;\n profile: RegistryEntry<DataGridHandle>;\n profile_diff: RegistryEntry<DataGridHandle>;\n value_diff: RegistryEntry<DataGridHandle>;\n value_diff_detail: RegistryEntry<DataGridHandle>;\n top_k_diff: RegistryEntry<HTMLDivElement>;\n histogram_diff: RegistryEntry<HTMLDivElement>;\n lineage_diff: RegistryEntry<never>;\n schema_diff: RegistryEntry<never>;\n sandbox: RegistryEntry<never>;\n simple: RegistryEntry<never>;\n}\n\n// ============================================================================\n// Registry\n// ============================================================================\n\n/**\n * The run type registry with all icons and available components.\n *\n * Components in @datarecce/ui are included directly. Components still in\n * OSS (QueryResultView, ProfileDiffResultView, ValueDiffResultView, etc.)\n * are undefined here and should be injected by consumers.\n *\n * @example\n * ```ts\n * const entry = registry.query;\n * console.log(entry.title); // \"Query\"\n * console.log(entry.icon); // TbSql\n * ```\n */\nexport const registry: RunRegistry = {\n lineage_diff: {\n title: \"Lineage Diff\",\n icon: TbBrandStackshare,\n },\n schema_diff: {\n title: \"Schema Diff\",\n icon: MdSchema,\n },\n query: {\n title: \"Query\",\n icon: TbSql,\n RunResultView:\n QueryResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n query_base: {\n title: \"Query Base\",\n icon: TbSql,\n RunResultView:\n QueryResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n query_diff: {\n title: \"Query Diff\",\n icon: TbSql,\n RunResultView:\n QueryDiffResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n row_count: {\n title: \"Row Count\",\n icon: MdFormatListNumberedRtl,\n RunResultView:\n RowCountResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n row_count_diff: {\n title: \"Row Count Diff\",\n icon: MdFormatListNumberedRtl,\n RunResultView:\n RowCountDiffResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n profile: {\n title: \"Profile\",\n icon: TbEyeSearch,\n RunResultView:\n ProfileResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n RunForm: ProfileDiffForm,\n },\n profile_diff: {\n title: \"Profile Diff\",\n icon: TbEyeSearch,\n RunResultView:\n ProfileDiffResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n RunForm: ProfileDiffForm,\n },\n value_diff: {\n title: \"Value Diff\",\n icon: TbAlignBoxLeftStretch,\n RunResultView:\n ValueDiffResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n RunForm: ValueDiffForm,\n },\n value_diff_detail: {\n title: \"Value Diff Detail\",\n icon: TbAlignBoxLeftStretch,\n RunResultView:\n ValueDiffDetailResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n RunForm: ValueDiffForm,\n },\n top_k_diff: {\n title: \"Top-K Diff\",\n icon: LuChartBarBig,\n RunResultView:\n TopKDiffResultView as RegistryEntry<HTMLDivElement>[\"RunResultView\"],\n RunForm: TopKDiffForm,\n },\n histogram_diff: {\n title: \"Histogram Diff\",\n icon: TbChartHistogram,\n RunResultView:\n HistogramDiffResultView as RegistryEntry<HTMLDivElement>[\"RunResultView\"],\n RunForm: HistogramDiffForm,\n },\n sandbox: {\n title: \"Sandbox\",\n icon: TbEyeEdit,\n },\n simple: {\n title: \"Simple\",\n icon: TbEyeEdit,\n },\n};\n\n// ============================================================================\n// Registry Lookup\n// ============================================================================\n\n/**\n * Find a run type configuration by run type.\n *\n * @param runType - The run type to look up\n * @returns The registry entry for the run type\n *\n * @example\n * ```ts\n * const entry = findByRunType(\"query\");\n * console.log(entry.title); // \"Query\"\n * console.log(entry.icon); // TbSql\n * ```\n */\nexport function findByRunType<T extends RunType>(runType: T): RunRegistry[T] {\n return registry[runType];\n}\n\n// ============================================================================\n// Legacy Exports (for backward compatibility)\n// ============================================================================\n\n/**\n * @deprecated Use `registry` directly instead\n */\nexport const defaultRunTypeConfig = registry;\n\n/**\n * Creates a run type registry with the provided configurations.\n * Merges with defaults to ensure all run types have entries.\n *\n * @param config - Partial or full registry configuration\n * @returns Complete registry with all run types\n *\n * @example\n * ```ts\n * const customRegistry = createRunTypeRegistry({\n * query: { ...registry.query, RunResultView: MyQueryResultView }\n * });\n * ```\n */\nexport function createRunTypeRegistry(\n config: Partial<Record<RunType, Partial<RunTypeConfig>>>,\n): RunRegistry {\n const result = { ...registry } as Record<RunType, RunTypeConfig>;\n\n for (const [type, overrides] of Object.entries(config) as [\n RunType,\n Partial<RunTypeConfig>,\n ][]) {\n if (overrides && type in result) {\n result[type] = {\n ...result[type],\n ...overrides,\n };\n }\n }\n\n return result as RunRegistry;\n}\n\n/**\n * Creates a bound lookup function for a specific registry.\n *\n * @param reg - The registry to bind\n * @returns A function that looks up run types in the bound registry\n *\n * @example\n * ```ts\n * const customRegistry = createRunTypeRegistry({ ... });\n * const findCustomRunType = createBoundFindByRunType(customRegistry);\n * const entry = findCustomRunType(\"query\");\n * ```\n */\nexport function createBoundFindByRunType(\n reg: RunRegistry,\n): <T extends RunType>(runType: T) => RunRegistry[T] {\n return <T extends RunType>(runType: T) => reg[runType];\n}\n","\"use client\";\n\n/**\n * @file run/RunListOss.tsx\n * @description OSS wrapper for RunList that injects OSS-specific dependencies.\n *\n * This component wraps the @datarecce/ui RunList with:\n * - Data fetching via React Query\n * - OSS-specific tracking\n * - Context integration (RecceActionContext, RecceInstanceContext)\n * - Navigation and check creation\n */\n\nimport IconButton from \"@mui/material/IconButton\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { useRouter } from \"next/navigation\";\nimport { useCallback, useMemo } from \"react\";\nimport { FaCheckCircle, FaRegCheckCircle } from \"react-icons/fa\";\nimport { PiX } from \"react-icons/pi\";\nimport { cacheKeys, createCheckByRun, listRuns, type Run } from \"../../api\";\nimport {\n useRecceActionContext,\n useRecceInstanceContext,\n useRouteConfig,\n} from \"../../contexts\";\nimport { useApiConfig } from \"../../hooks\";\nimport { trackHistoryAction } from \"../../lib/api/track\";\nimport { RunList as BaseRunList, type RunListItemData } from \"./RunList\";\nimport { findByRunType } from \"./registry\";\n\n/**\n * Transform API Run to RunListItemData for the UI component\n */\nfunction mapRunToListItem(run: Run): RunListItemData {\n return {\n id: run.run_id,\n name: run.name,\n type: run.type,\n // Default to \"Finished\" if status is not set (shouldn't happen in practice)\n status: run.status ?? \"Finished\",\n runAt: run.run_at,\n checkId: run.check_id,\n };\n}\n\n/**\n * RunListOss Component - OSS wrapper\n *\n * Provides the History panel with run list, integrating with OSS-specific\n * contexts, tracking, and navigation.\n *\n * @example\n * ```tsx\n * <RunListOss />\n * ```\n */\nexport function RunListOss() {\n const { closeHistory, showRunId, runId } = useRecceActionContext();\n const { featureToggles } = useRecceInstanceContext();\n const { apiClient } = useApiConfig();\n const router = useRouter();\n const queryClient = useQueryClient();\n const { basePath } = useRouteConfig();\n\n // Fetch all runs\n const { data: runs, isLoading } = useQuery({\n queryKey: cacheKeys.runs(),\n queryFn: async () => {\n // Cast from library Run[] to OSS Run[] for discriminated union support\n return (await listRuns(apiClient)) as Run[];\n },\n retry: false,\n });\n\n // Transform runs to list item data format\n const runListItems = useMemo(() => {\n return (runs ?? []).map(mapRunToListItem);\n }, [runs]);\n\n // Handle run selection with tracking\n const handleRunSelect = useCallback(\n (selectedRunId: string) => {\n trackHistoryAction({ name: \"click_run\" });\n showRunId(selectedRunId, false);\n },\n [showRunId],\n );\n\n // Handle add to checklist with tracking and navigation\n const handleAddToChecklist = useCallback(\n async (clickedRunId: string) => {\n trackHistoryAction({ name: \"add_to_checklist\" });\n const check = await createCheckByRun(clickedRunId, undefined, apiClient);\n await queryClient.invalidateQueries({ queryKey: cacheKeys.checks() });\n router.push(`${basePath}/checks/?id=${check.check_id}`);\n },\n [apiClient, queryClient, router.push, basePath],\n );\n\n // Handle go to check with tracking\n const handleGoToCheck = useCallback(\n (checkId: string) => {\n trackHistoryAction({ name: \"go_to_check\" });\n router.push(`${basePath}/checks/?id=${checkId}`);\n },\n [router.push, basePath],\n );\n\n // Handle close history with tracking\n const handleCloseHistory = useCallback(() => {\n trackHistoryAction({ name: \"hide\" });\n closeHistory();\n }, [closeHistory]);\n\n // Get icon for run type\n const getRunIcon = useCallback((runType: string) => {\n const registryEntry = findByRunType(\n runType as Parameters<typeof findByRunType>[0],\n );\n const IconComponent = registryEntry?.icon;\n return IconComponent ? <IconComponent /> : null;\n }, []);\n\n // Header action - close button\n const headerActions = (\n <IconButton aria-label=\"Close History\" onClick={handleCloseHistory}>\n <PiX />\n </IconButton>\n );\n\n return (\n <BaseRunList\n runs={runListItems}\n selectedId={runId}\n isLoading={isLoading}\n onRunSelect={handleRunSelect}\n onAddToChecklist={handleAddToChecklist}\n onGoToCheck={handleGoToCheck}\n getRunIcon={getRunIcon}\n hideAddToChecklist={featureToggles.disableUpdateChecklist}\n title=\"History\"\n headerActions={headerActions}\n emptyMessage=\"No runs\"\n loadingMessage=\"Loading...\"\n groupByDate={true}\n addToChecklistIcon={<FaRegCheckCircle />}\n goToCheckIcon={<FaCheckCircle color=\"green\" />}\n />\n );\n}\n","\"use client\";\n\n/**\n * @file run/RunModal.tsx\n * @description Modal dialog for configuring and executing runs with forms.\n *\n * This component provides a generic modal for run form interactions.\n * OSS-specific behavior (tracking, documentation URLs) is injected via props.\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Link from \"@mui/material/Link\";\nimport MuiPopover from \"@mui/material/Popover\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport { type ComponentType, ReactNode, useRef, useState } from \"react\";\nimport { IconBaseProps } from \"react-icons\";\nimport { IoClose } from \"react-icons/io5\";\nimport type { Run, RunType } from \"../../api\";\nimport type { RunFormProps } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Props for the RunModal component.\n *\n * @template PT - The type of form parameters used by the RunForm component\n */\nexport interface RunModalProps<PT = unknown> {\n /** Whether the modal is currently open */\n isOpen: boolean;\n\n /** Callback when the modal is closed (via X button, backdrop click, or escape key) */\n onClose: () => void;\n\n /** Callback when the execute button is clicked with valid parameters */\n onExecute: (type: RunType, params: PT) => void;\n\n /** The title displayed in the modal header */\n title: string;\n\n /** The run type being configured */\n type: RunType;\n\n /** Initial/default parameters for the form */\n params?: PT;\n\n /** The initial run to display (for edit scenarios) */\n initialRun?: Run;\n\n /** The form component to render for configuring run parameters */\n RunForm?: ComponentType<RunFormProps<PT>>;\n\n /**\n * Optional callback when the modal is cancelled (X button clicked without executing).\n * Use this for analytics/tracking of form cancellations.\n */\n onCancel?: () => void;\n\n /**\n * Optional callback when execute is clicked.\n * Use this for analytics/tracking of form submissions.\n */\n onExecuteClick?: () => void;\n\n /**\n * Optional documentation URL for the run type.\n * If provided, an info icon will be shown that links to the documentation.\n */\n documentationUrl?: string | null;\n\n /**\n * Optional icon component to display next to the documentation link.\n * Can be any component - will be wrapped by MUI Box with fontSize styling.\n * Compatible with react-icons, custom SVG components, or MUI icons.\n */\n InfoIcon?: ComponentType<IconBaseProps>;\n}\n\n// ============================================================================\n// Default Info Icon\n// ============================================================================\n\n/**\n * Default info icon component - a simple info circle.\n * OSS and Cloud consumers can override with their own icons.\n */\nconst DefaultInfoIcon = ({ size = 16 }: { size?: string | number }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n </svg>\n);\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * RunModal - A dialog for configuring and executing runs.\n *\n * This component renders a modal dialog with:\n * - A title with optional documentation link\n * - A form for configuring run parameters (optional)\n * - Execute and close actions\n *\n * The modal supports dependency injection for:\n * - Form component (`RunForm`) - for run-type-specific parameter forms\n * - Tracking callbacks (`onCancel`, `onExecuteClick`) - for analytics\n * - Documentation URL (`documentationUrl`) - for help links\n * - Info icon (`InfoIcon`) - for customizing the documentation link icon\n *\n * @example\n * ```tsx\n * <RunModal\n * isOpen={isOpen}\n * onClose={handleClose}\n * onExecute={handleExecute}\n * title=\"Profile Diff\"\n * type=\"profile_diff\"\n * params={{ model: \"my_model\" }}\n * RunForm={ProfileDiffForm}\n * documentationUrl=\"https://docs.reccehq.com/features/lineage/#profile-diff\"\n * />\n * ```\n */\nexport function RunModal<PT = unknown>({\n isOpen,\n onClose,\n onExecute,\n type,\n title,\n params: defaultParams,\n RunForm,\n onCancel,\n onExecuteClick,\n documentationUrl,\n InfoIcon = DefaultInfoIcon,\n}: RunModalProps<PT>) {\n const [params, setParams] = useState<Partial<PT>>(\n (defaultParams ?? {}) as Partial<PT>,\n );\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);\n const [isReadyToExecute, setIsReadyToExecute] = useState(false);\n const executeClicked = useRef(false);\n\n const handleClose = () => {\n if (!executeClicked.current) {\n // Track cancellation if callback provided\n onCancel?.();\n }\n executeClicked.current = false; // Reset for next open\n onClose();\n };\n\n const handleExecuteClick = () => {\n executeClicked.current = true;\n // Track execute click if callback provided\n onExecuteClick?.();\n onExecute(type, params as PT);\n };\n\n return (\n <MuiDialog\n open={isOpen}\n onClose={handleClose}\n maxWidth=\"sm\"\n fullWidth\n scroll=\"paper\"\n slotProps={{\n paper: { sx: { height: \"75%\", minHeight: \"400px\" } },\n }}\n >\n <DialogTitle sx={{ display: \"flex\", alignItems: \"center\" }}>\n {title}{\" \"}\n {documentationUrl && (\n <>\n <IconButton\n size=\"small\"\n aria-label=\"Click this button to learn more about the SQL behind\"\n onMouseEnter={(e) => setAnchorEl(e.currentTarget)}\n onMouseLeave={() => setAnchorEl(null)}\n onClick={() => window.open(documentationUrl, \"_blank\")}\n >\n <Box component={InfoIcon} sx={{ fontSize: \"16px\" }} />\n </IconButton>\n <MuiPopover\n open={Boolean(anchorEl)}\n anchorEl={anchorEl}\n onClose={() => setAnchorEl(null)}\n anchorOrigin={{\n vertical: \"bottom\",\n horizontal: \"right\",\n }}\n transformOrigin={{\n vertical: \"top\",\n horizontal: \"right\",\n }}\n disableRestoreFocus\n sx={{ pointerEvents: \"none\" }}\n slotProps={{\n paper: {\n sx: { bgcolor: \"black\", color: \"white\", p: 1 },\n },\n }}\n >\n <Typography sx={{ fontSize: \"0.875rem\" }}>\n Click{\" \"}\n <Link\n href={documentationUrl}\n target=\"_blank\"\n sx={{\n textDecoration: \"underline\",\n color: \"white\",\n \"&:hover\": { color: \"iochmara.300\" },\n }}\n >\n here\n </Link>{\" \"}\n to learn more about the SQL behind\n </Typography>\n </MuiPopover>\n </>\n )}\n </DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={handleClose}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n <DialogContent\n sx={{\n p: 0,\n overflow: \"auto\",\n borderTop: \"1px solid\",\n borderBottom: \"1px solid\",\n borderColor: \"divider\",\n }}\n >\n <Box sx={{ contain: \"layout\" }}>\n {RunForm && (\n <RunForm\n params={params}\n onParamsChanged={setParams}\n setIsReadyToExecute={setIsReadyToExecute}\n />\n )}\n </Box>\n </DialogContent>\n <DialogActions>\n <Stack direction=\"row\" spacing=\"10px\">\n <Button\n disabled={!isReadyToExecute}\n color=\"iochmara\"\n variant=\"contained\"\n onClick={handleExecuteClick}\n >\n Execute\n </Button>\n </Stack>\n </DialogActions>\n </MuiDialog>\n );\n}\n","/**\n * @file run/RunModalOss.tsx\n * @description OSS wrapper for RunModal from @datarecce/ui.\n *\n * This wrapper injects OSS-specific behavior:\n * - Tracking callbacks for analytics (Amplitude)\n * - Documentation URL mapping for run types\n */\n\nimport type { ComponentType } from \"react\";\nimport { PiInfo } from \"react-icons/pi\";\nimport type { Run, RunType } from \"../../api\";\nimport {\n EXPLORE_FORM_EVENT,\n isExploreAction,\n trackExploreActionForm,\n} from \"../../lib/api/track\";\nimport type { RunModalProps as UIRunModalProps } from \"./RunModal\";\nimport { RunModal as UIRunModal } from \"./RunModal\";\nimport type { RunFormParamTypes, RunFormProps } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * OSS-specific props for RunModal.\n * Extends the base props with OSS form parameter types.\n */\nexport interface RunModalProps {\n /** Whether the modal is currently open */\n isOpen: boolean;\n\n /** Callback when the modal is closed */\n onClose: () => void;\n\n /** Callback when the execute button is clicked */\n onExecute: (type: RunType, params: RunFormParamTypes) => void;\n\n /** The title displayed in the modal header */\n title: string;\n\n /** The run type being configured */\n type: RunType;\n\n /** Initial/default parameters for the form */\n params?: RunFormParamTypes;\n\n /** The initial run to display (for edit scenarios) */\n initialRun?: Run;\n\n /** The form component to render for configuring run parameters */\n RunForm?: ComponentType<RunFormProps<RunFormParamTypes>>;\n}\n\n// ============================================================================\n// Documentation URL Mapping\n// ============================================================================\n\n/**\n * Maps run types to their documentation URLs.\n * Returns null for run types without documentation.\n */\nconst getDocumentationUrl = (type: RunType): string | null => {\n const urlMap: Record<string, string> = {\n value_diff: \"https://docs.reccehq.com/features/lineage/#value-diff\",\n profile_diff: \"https://docs.reccehq.com/features/lineage/#profile-diff\",\n histogram_diff: \"https://docs.reccehq.com/features/lineage/#histogram-diff\",\n top_k_diff: \"https://docs.reccehq.com/features/lineage/#top-k-diff\",\n };\n return urlMap[type] || null;\n};\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * OSS RunModal - Wraps @datarecce/ui's RunModal with OSS-specific behavior.\n *\n * This wrapper:\n * - Injects tracking callbacks for form cancellation and execution\n * - Provides documentation URLs based on run type\n * - Uses the OSS IconInfo component for the documentation link\n *\n * @example\n * ```tsx\n * <RunModal\n * isOpen={isOpen}\n * onClose={handleClose}\n * onExecute={handleExecute}\n * title=\"Profile Diff\"\n * type=\"profile_diff\"\n * params={{ model: \"my_model\" }}\n * RunForm={ProfileDiffForm}\n * />\n * ```\n */\nexport function RunModalOss({\n isOpen,\n onClose,\n onExecute,\n type,\n title,\n params,\n initialRun,\n RunForm,\n}: RunModalProps) {\n // Track form cancellation for explore actions\n const handleCancel = () => {\n if (isExploreAction(type)) {\n trackExploreActionForm({\n action: type,\n event: EXPLORE_FORM_EVENT.CANCEL,\n });\n }\n };\n\n // Track form execution for explore actions\n const handleExecuteClick = () => {\n if (isExploreAction(type)) {\n trackExploreActionForm({\n action: type,\n event: EXPLORE_FORM_EVENT.EXECUTE,\n });\n }\n };\n\n return (\n <UIRunModal<RunFormParamTypes>\n isOpen={isOpen}\n onClose={onClose}\n onExecute={onExecute}\n type={type}\n title={title}\n params={params}\n initialRun={initialRun}\n RunForm={RunForm as UIRunModalProps<RunFormParamTypes>[\"RunForm\"]}\n onCancel={handleCancel}\n onExecuteClick={handleExecuteClick}\n documentationUrl={getDocumentationUrl(type)}\n InfoIcon={PiInfo}\n />\n );\n}\n","\"use client\";\n\n/**\n * @file run/RunView.tsx\n * @description Generic run view component for displaying run execution state and results.\n *\n * This component provides:\n * - Loading state with progress indicator\n * - Error state display\n * - Run result rendering via RunResultView component or children render prop\n * - Dependency injection for error boundaries (OSS uses Sentry, others can use custom)\n *\n * @example Basic usage with RunResultView\n * ```tsx\n * import { RunView } from \"@datarecce/ui/components/run\";\n * import { QueryResultView } from \"./QueryResultView\";\n *\n * function MyComponent() {\n * return (\n * <RunView\n * run={run}\n * isRunning={isRunning}\n * RunResultView={QueryResultView}\n * onCancel={handleCancel}\n * />\n * );\n * }\n * ```\n *\n * @example With children render prop\n * ```tsx\n * <RunView run={run} isRunning={isRunning}>\n * {({ run, viewOptions, onViewOptionsChanged }) => (\n * <CustomResultView run={run} viewOptions={viewOptions} />\n * )}\n * </RunView>\n * ```\n *\n * @example With error boundary injection (OSS pattern)\n * ```tsx\n * import { ErrorBoundary } from \"@datarecce/ui/components/errorboundary\";\n * import ResultErrorFallback from \"@datarecce/ui/lib/result/ResultErrorFallback\";\n *\n * <RunView\n * run={run}\n * ErrorBoundary={ErrorBoundary}\n * errorBoundaryFallback={ResultErrorFallback}\n * />\n * ```\n */\n\nimport MuiAlert from \"@mui/material/Alert\";\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport type {\n ComponentType,\n ForwardRefExoticComponent,\n ReactNode,\n Ref,\n RefAttributes,\n} from \"react\";\nimport { forwardRef } from \"react\";\n\nimport type { Run } from \"../../api\";\nimport { useIsDark } from \"../../hooks\";\nimport type { RunResultViewProps } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * API error shape for extracting error messages from axios responses.\n */\ninterface ApiError {\n response?: {\n data?: {\n detail?: string;\n };\n };\n}\n\n/**\n * Props for the error boundary wrapper component.\n * Compatible with Sentry ErrorBoundary and custom implementations.\n */\nexport interface ErrorBoundaryWrapperProps {\n /** The content to wrap with error boundary */\n children: ReactNode;\n /** Fallback to display when an error occurs */\n // biome-ignore lint/suspicious/noExplicitAny: Fallback type varies by implementation (Sentry FallbackRender, React element, etc.)\n fallback?: any;\n}\n\n/**\n * Props for the RunView component.\n *\n * Uses permissive types to support various run result view components.\n * Consumers can pass typed RunResultView components and the types will\n * be inferred correctly at the call site.\n */\nexport interface RunViewProps {\n /** Whether a run is currently executing */\n isRunning?: boolean;\n\n /** The run object containing execution state and results */\n run?: Run;\n\n /** Error that occurred during run execution */\n error?: Error | null;\n\n /** Progress information for the current run */\n progress?: Run[\"progress\"];\n\n /** Whether the run is being aborted */\n isAborting?: boolean;\n\n /**\n * Whether this is a check detail view.\n * @deprecated This prop may be removed in future versions.\n */\n isCheckDetail?: boolean;\n\n /** Callback when user cancels the run */\n onCancel?: () => void;\n\n /** Callback to execute/re-execute the run */\n onExecuteRun?: () => void;\n\n /** Current view options for result display */\n // biome-ignore lint/suspicious/noExplicitAny: View options type varies by run type\n viewOptions?: any;\n\n /** Callback when view options change */\n // biome-ignore lint/suspicious/noExplicitAny: View options type varies by run type\n onViewOptionsChanged?: (viewOptions: any) => void;\n\n /**\n * Component to render run results.\n * Either RunResultView or children is required.\n */\n RunResultView?: ForwardRefExoticComponent<\n // biome-ignore lint/suspicious/noExplicitAny: RunResultView types vary by run type\n RunResultViewProps<any> & RefAttributes<any>\n >;\n\n /**\n * Render prop for custom result rendering.\n * Either RunResultView or children is required.\n */\n // biome-ignore lint/suspicious/noExplicitAny: Render prop view options vary by run type\n children?: (params: RunResultViewProps<any>) => ReactNode;\n\n // ============================================================================\n // Dependency Injection Props\n // ============================================================================\n\n /**\n * Error boundary component to wrap the result view.\n * If not provided, results are rendered without error boundary.\n *\n * @example Using Sentry ErrorBoundary\n * ```tsx\n * import { ErrorBoundary } from \"@sentry/react\";\n * <RunView ErrorBoundary={ErrorBoundary} />\n * ```\n */\n ErrorBoundary?: ComponentType<ErrorBoundaryWrapperProps>;\n\n /**\n * Fallback element/render function for the error boundary.\n * Type depends on the ErrorBoundary implementation used.\n *\n * @example Using Sentry FallbackRender\n * ```tsx\n * import ResultErrorFallback from \"@datarecce/ui/lib/result/ResultErrorFallback\";\n * <RunView errorBoundaryFallback={ResultErrorFallback} />\n * ```\n */\n // biome-ignore lint/suspicious/noExplicitAny: Fallback type varies by error boundary implementation\n errorBoundaryFallback?: any;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Generic run view component that displays run execution state and results.\n *\n * States:\n * 1. **Error state**: Shows error message from API response or run.error\n * 2. **Running state**: Shows loading spinner with progress and cancel button\n * 3. **Loading state**: Shows spinner when run is undefined\n * 4. **Result state**: Renders RunResultView or children with run results\n *\n * @remarks\n * The component uses forwardRef to pass refs to the RunResultView component,\n * enabling features like screenshot capture for data grids.\n *\n * @example\n * ```tsx\n * const ref = useRef<DataGridHandle>(null);\n *\n * <RunView\n * ref={ref}\n * run={run}\n * RunResultView={QueryResultView}\n * viewOptions={viewOptions}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const RunView = forwardRef<unknown, RunViewProps>(function RunView(\n {\n isRunning,\n isAborting,\n progress,\n error,\n run,\n onCancel,\n viewOptions,\n onViewOptionsChanged,\n RunResultView,\n children,\n ErrorBoundary,\n errorBoundaryFallback,\n },\n ref,\n) {\n const isDark = useIsDark();\n const errorMessage =\n (error as ApiError | undefined)?.response?.data?.detail ?? run?.error;\n\n // ============================================================================\n // Error State\n // ============================================================================\n if (errorMessage) {\n return (\n <MuiAlert\n severity=\"error\"\n sx={\n isDark\n ? {\n bgcolor: \"error.dark\",\n color: \"common.white\",\n \"& .MuiAlert-icon\": {\n color: \"common.white\",\n },\n }\n : undefined\n }\n >\n Error: <span className=\"no-track-pii-safe\">{errorMessage}</span>\n </MuiAlert>\n );\n }\n\n // ============================================================================\n // Running State\n // ============================================================================\n if (isRunning || run?.status === \"Running\") {\n let loadingMessage = \"Loading...\";\n if (progress?.message) {\n loadingMessage = progress.message;\n } else if (run?.progress?.message) {\n loadingMessage = run.progress.message;\n }\n\n const progressValue =\n progress?.percentage != null ? progress.percentage * 100 : undefined;\n\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n p: \"1rem\",\n height: \"100%\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n }}\n >\n <Stack spacing={2} alignItems=\"center\">\n <Stack direction=\"row\" alignItems=\"center\" spacing={1}>\n {progressValue == null ? (\n <CircularProgress size={32} />\n ) : (\n <Box sx={{ position: \"relative\", display: \"inline-flex\" }}>\n <CircularProgress\n variant=\"determinate\"\n value={progressValue}\n size={32}\n />\n <Box\n sx={{\n top: 0,\n left: 0,\n bottom: 0,\n right: 0,\n position: \"absolute\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Typography\n variant=\"caption\"\n component=\"div\"\n sx={{ fontSize: \"0.6rem\" }}\n >\n {`${Math.round(progressValue)}%`}\n </Typography>\n </Box>\n </Box>\n )}\n\n {isAborting ? (\n <Typography>Aborting...</Typography>\n ) : (\n <Typography className=\"no-track-pii-safe\">\n {loadingMessage}\n </Typography>\n )}\n </Stack>\n {!isAborting && (\n <Button variant=\"contained\" onClick={onCancel} size=\"small\">\n Cancel\n </Button>\n )}\n </Stack>\n </Box>\n );\n }\n\n // ============================================================================\n // Loading State (No Run Yet)\n // ============================================================================\n if (!run) {\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n height: \"100%\",\n }}\n >\n <CircularProgress size={32} />\n </Box>\n );\n }\n\n // ============================================================================\n // Validation\n // ============================================================================\n if (children && RunResultView) {\n throw new Error(\n \"RunView requires either a children or a RunResultView prop, but not both.\",\n );\n }\n if (!children && !RunResultView) {\n throw new Error(\n \"RunView requires at least one of children or RunResultView prop.\",\n );\n }\n\n // ============================================================================\n // Result State\n // ============================================================================\n\n /**\n * Renders the result content, optionally wrapped with an error boundary.\n */\n const renderResultContent = () => {\n const resultView =\n RunResultView && (run.error ?? run.result) ? (\n <RunResultView\n ref={ref}\n run={run}\n viewOptions={viewOptions}\n onViewOptionsChanged={onViewOptionsChanged}\n />\n ) : null;\n\n const childContent = children?.({ run, viewOptions, onViewOptionsChanged });\n\n // If ErrorBoundary is provided, wrap the content\n if (ErrorBoundary && resultView) {\n return (\n <>\n <ErrorBoundary fallback={errorBoundaryFallback}>\n {resultView}\n </ErrorBoundary>\n {childContent}\n </>\n );\n }\n\n // Otherwise render without error boundary\n return (\n <>\n {resultView}\n {childContent}\n </>\n );\n };\n\n return (\n <Box\n sx={{\n height: \"100%\",\n contain: \"layout\",\n overflow: \"auto\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n }}\n className=\"no-track-pii-safe\"\n >\n {renderResultContent()}\n </Box>\n );\n});\n\n// Set display name for debugging\nRunView.displayName = \"RunView\";\n","\"use client\";\n\n/**\n * @file run/RunResultPane.tsx\n * @description Reusable run result pane component with dependency injection support.\n *\n * This component provides the core UI for displaying run results including:\n * - Tab navigation (Result, Params, Query)\n * - Run status display\n * - Export/Share menus (injectable)\n * - Add to checklist functionality (injectable)\n *\n * OSS-specific behaviors are injected via props to maintain platform independence.\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Divider from \"@mui/material/Divider\";\nimport IconButton from \"@mui/material/IconButton\";\nimport ListItemIcon from \"@mui/material/ListItemIcon\";\nimport ListItemText from \"@mui/material/ListItemText\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Stack from \"@mui/material/Stack\";\nimport Tab from \"@mui/material/Tab\";\nimport Tabs from \"@mui/material/Tabs\";\nimport Typography from \"@mui/material/Typography\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport {\n type ComponentType,\n type ForwardRefExoticComponent,\n type MouseEvent,\n memo,\n type ReactNode,\n type Ref,\n type RefAttributes,\n useCallback,\n useState,\n} from \"react\";\nimport { IoClose } from \"react-icons/io5\";\nimport {\n PiCaretDown,\n PiCheck,\n PiClipboardText,\n PiDownloadSimple,\n PiImage,\n PiRepeat,\n PiTable,\n} from \"react-icons/pi\";\nimport { TbCloudUpload } from \"react-icons/tb\";\nimport YAML from \"yaml\";\nimport type { Run, RunParamTypes } from \"../../api\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\nimport { CodeEditor } from \"../../primitives\";\nimport { RunView } from \"./RunView\";\nimport type { RunResultViewProps, RunResultViewRef } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Tab values for the run result pane\n */\nexport type RunResultPaneTabValue = \"result\" | \"params\" | \"query\";\n\n/**\n * Props for CSV export functionality\n */\nexport interface CSVExportProps {\n /** Whether CSV export is available */\n canExportCSV: boolean;\n /** Copy data as CSV to clipboard */\n copyAsCSV: () => Promise<void>;\n /** Copy data as TSV to clipboard (pastes into spreadsheets) */\n copyAsTSV?: () => Promise<void>;\n /** Download data as CSV file */\n downloadAsCSV: () => void;\n /** Download data as TSV file */\n downloadAsTSV?: () => void;\n /** Download data as Excel file */\n downloadAsExcel?: () => void;\n}\n\n/**\n * Props for the export menu component\n */\nexport interface RunResultExportMenuProps {\n /** The current run */\n run?: Run;\n /** Whether export is disabled */\n disableExport: boolean;\n /** Handler for copy as image */\n onCopyAsImage: () => Promise<void>;\n /** Handler for mouse enter (for highlight effect) */\n onMouseEnter?: () => void;\n /** Handler for mouse leave (for highlight effect) */\n onMouseLeave?: () => void;\n /** CSV export functionality */\n csvExport?: CSVExportProps;\n}\n\n/**\n * Props for the share menu component\n */\nexport interface RunResultShareMenuProps extends RunResultExportMenuProps {\n /** Whether user is authenticated */\n authed?: boolean;\n /** Handler for share to cloud */\n onShareToCloud?: () => Promise<void>;\n /** Handler for showing auth modal when not authenticated */\n onShowAuthModal?: () => void;\n}\n\n/**\n * Props for the Add to Check button\n */\nexport interface AddToCheckButtonProps {\n /** The current run ID */\n runId?: string;\n /** The current run */\n run?: Run;\n /** Whether the button is disabled due to feature toggle */\n disableUpdateChecklist?: boolean;\n /** Whether there's an error */\n hasError?: boolean;\n /** Handler for navigating to existing check */\n onGoToCheck?: (checkId: string) => void;\n /** Handler for adding run to checklist */\n onAddToChecklist?: () => Promise<void>;\n}\n\n/**\n * Props for the single environment setup notification\n */\nexport interface SingleEnvironmentNotificationProps {\n /** The run type */\n runType?: string;\n /** Component to render the notification */\n NotificationComponent?: ComponentType<{\n runType?: string;\n onClose: () => void;\n }>;\n}\n\n/**\n * Props for the SQL editor components\n */\nexport interface SqlEditorProps {\n /** SQL query value */\n value: string;\n /** Base SQL query value (for diff views) */\n baseValue?: string;\n /** Whether the editor is read-only */\n readOnly?: boolean;\n}\n\n/**\n * Props for RunResultPane component.\n * Uses dependency injection for OSS-specific behavior.\n */\nexport interface RunResultPaneProps<VO = unknown, RefType = unknown> {\n // ============================================================================\n // Core Data\n // ============================================================================\n\n /** The run ID */\n runId?: string;\n\n /** The run object */\n run?: Run;\n\n /** Whether the run is currently executing */\n isRunning?: boolean;\n\n /** Error object if run failed */\n error?: Error | null;\n\n // ============================================================================\n // View Configuration\n // ============================================================================\n\n /** Current view options */\n viewOptions?: VO;\n\n /** Callback when view options change */\n onViewOptionsChanged?: (viewOptions: VO) => void;\n\n /** Whether this is a single environment (base not configured) */\n isSingleEnvironment?: boolean;\n\n // ============================================================================\n // Feature Toggles\n // ============================================================================\n\n /** Disable database query execution */\n disableDatabaseQuery?: boolean;\n\n /** Disable share functionality (show export menu instead) */\n disableShare?: boolean;\n\n /** Disable update checklist functionality */\n disableUpdateChecklist?: boolean;\n\n // ============================================================================\n // Event Handlers\n // ============================================================================\n\n /** Handler for closing the pane */\n onClose?: () => void;\n\n /** Handler for cancelling a running query */\n onCancel?: () => void;\n\n /** Handler for re-running the query */\n onRerun?: () => void;\n\n // ============================================================================\n // Export/Share Handlers (Dependency Injection)\n // ============================================================================\n\n /** Handler for copying as image */\n onCopyAsImage?: () => Promise<void>;\n\n /** Mouse enter handler for copy button highlight */\n onCopyMouseEnter?: () => void;\n\n /** Mouse leave handler for copy button highlight */\n onCopyMouseLeave?: () => void;\n\n /** CSV export functionality */\n csvExport?: CSVExportProps;\n\n /** Whether user is authenticated (for share menu) */\n authed?: boolean;\n\n /** Handler for share to cloud */\n onShareToCloud?: () => Promise<void>;\n\n /** Handler for showing auth modal */\n onShowAuthModal?: () => void;\n\n /** Optional tracking callback for copy to clipboard */\n onTrackCopyToClipboard?: (type: string, from: string) => void;\n\n // ============================================================================\n // Checklist Handlers (Dependency Injection)\n // ============================================================================\n\n /** Handler for navigating to existing check */\n onGoToCheck?: (checkId: string) => void;\n\n /** Handler for adding run to checklist */\n onAddToChecklist?: () => Promise<void>;\n\n // ============================================================================\n // Custom Components (Dependency Injection)\n // ============================================================================\n\n /** Custom notification component for single environment setup */\n SingleEnvironmentNotification?: ComponentType<{\n runType?: string;\n onClose: () => void;\n }>;\n\n /** Custom SQL editor component */\n SqlEditorComponent?: ComponentType<SqlEditorProps>;\n\n /** Custom dual SQL editor component (for query diff) */\n DualSqlEditorComponent?: ComponentType<SqlEditorProps>;\n\n /** Custom auth modal component */\n AuthModalComponent?: ComponentType<{\n open: boolean;\n onClose: () => void;\n }>;\n\n /** Result view component from registry */\n RunResultView?: ForwardRefExoticComponent<\n RunResultViewProps<VO> & RefAttributes<RefType>\n >;\n\n /** Ref for the result view (for screenshots) */\n resultViewRef?: Ref<RefType>;\n\n // ============================================================================\n // Children (Alternative to RunResultView)\n // ============================================================================\n\n /** Custom result renderer */\n children?: (props: {\n run: Run;\n viewOptions?: VO;\n onViewOptionsChanged?: (viewOptions: VO) => void;\n }) => ReactNode;\n}\n\n// ============================================================================\n// Internal Components\n// ============================================================================\n\n/**\n * Params view component - displays run parameters as YAML\n */\nconst ParamView = memo(\n ({ type, params }: { type: string; params: RunParamTypes }) => {\n const isDark = useIsDark();\n const yaml = YAML.stringify({ type, params }, null, 2);\n return (\n <CodeEditor\n value={yaml}\n language=\"yaml\"\n readOnly={true}\n lineNumbers={false}\n wordWrap={true}\n fontSize={14}\n height=\"100%\"\n theme={isDark ? \"dark\" : \"light\"}\n className=\"no-track-pii-safe\"\n />\n );\n },\n);\nParamView.displayName = \"ParamView\";\n\n/**\n * Default export menu component\n */\nconst DefaultExportMenu = memo(\n ({\n disableExport,\n onCopyAsImage,\n onMouseEnter,\n onMouseLeave,\n csvExport,\n }: RunResultExportMenuProps) => {\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);\n const open = Boolean(anchorEl);\n\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(event.currentTarget);\n };\n\n const handleClose = () => {\n setAnchorEl(null);\n };\n\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"neutral\"\n onClick={handleClick}\n endIcon={<PiCaretDown />}\n sx={{ textTransform: \"none\" }}\n >\n Export\n </Button>\n <Menu anchorEl={anchorEl} open={open} onClose={handleClose}>\n <MenuItem\n onClick={async () => {\n await onCopyAsImage();\n handleClose();\n }}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n disabled={disableExport}\n >\n <ListItemIcon>\n <PiImage />\n </ListItemIcon>\n <ListItemText>Copy as Image</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={async () => {\n await csvExport?.copyAsTSV?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiClipboardText />\n </ListItemIcon>\n <ListItemText>Copy as Text</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={async () => {\n await csvExport?.copyAsCSV();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiTable />\n </ListItemIcon>\n <ListItemText>Copy as CSV</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsCSV();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as CSV</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsTSV?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as TSV</ListItemText>\n </MenuItem>\n {csvExport?.downloadAsExcel && (\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsExcel?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as Excel</ListItemText>\n </MenuItem>\n )}\n </Menu>\n </>\n );\n },\n);\nDefaultExportMenu.displayName = \"DefaultExportMenu\";\n\n/**\n * Default share menu component\n */\nconst DefaultShareMenu = memo(\n ({\n disableExport,\n onCopyAsImage,\n onMouseEnter,\n onMouseLeave,\n csvExport,\n authed,\n onShareToCloud,\n onShowAuthModal,\n }: RunResultShareMenuProps) => {\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);\n const open = Boolean(anchorEl);\n\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(event.currentTarget);\n };\n\n const handleClose = () => {\n setAnchorEl(null);\n };\n\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"neutral\"\n onClick={handleClick}\n endIcon={<PiCaretDown />}\n sx={{ textTransform: \"none\" }}\n >\n Share\n </Button>\n <Menu anchorEl={anchorEl} open={open} onClose={handleClose}>\n <MenuItem\n onClick={async () => {\n await onCopyAsImage();\n handleClose();\n }}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n disabled={disableExport}\n >\n <ListItemIcon>\n <PiImage />\n </ListItemIcon>\n <ListItemText>Copy as Image</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={async () => {\n await csvExport?.copyAsTSV?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiClipboardText />\n </ListItemIcon>\n <ListItemText>Copy as Text</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={async () => {\n await csvExport?.copyAsCSV();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiTable />\n </ListItemIcon>\n <ListItemText>Copy as CSV</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsCSV();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as CSV</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsTSV?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as TSV</ListItemText>\n </MenuItem>\n {csvExport?.downloadAsExcel && (\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsExcel?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as Excel</ListItemText>\n </MenuItem>\n )}\n <Divider />\n {authed ? (\n <MenuItem\n onClick={async () => {\n await onShareToCloud?.();\n handleClose();\n }}\n >\n <ListItemIcon>\n <TbCloudUpload />\n </ListItemIcon>\n <ListItemText>Share to Cloud</ListItemText>\n </MenuItem>\n ) : (\n <MenuItem\n onClick={() => {\n onShowAuthModal?.();\n handleClose();\n }}\n >\n <ListItemIcon>\n <TbCloudUpload />\n </ListItemIcon>\n <ListItemText>Share</ListItemText>\n </MenuItem>\n )}\n </Menu>\n </>\n );\n },\n);\nDefaultShareMenu.displayName = \"DefaultShareMenu\";\n\n/**\n * Default Add to Check button component\n */\nconst DefaultAddToCheckButton = memo(\n ({\n runId,\n run,\n disableUpdateChecklist,\n hasError,\n onGoToCheck,\n onAddToChecklist,\n }: AddToCheckButtonProps) => {\n const checkId = run?.check_id;\n const disabled = !runId || !run?.result || hasError;\n\n if (disableUpdateChecklist) {\n return null;\n }\n\n if (checkId) {\n return (\n <Button\n disabled={disabled}\n size=\"small\"\n variant=\"contained\"\n onClick={() => onGoToCheck?.(checkId)}\n startIcon={<PiCheck />}\n sx={{ textTransform: \"none\" }}\n >\n Go to Check\n </Button>\n );\n }\n\n return (\n <Button\n disabled={disabled}\n size=\"small\"\n variant=\"contained\"\n onClick={onAddToChecklist}\n startIcon={<PiCheck />}\n sx={{ textTransform: \"none\" }}\n >\n Add to Checklist\n </Button>\n );\n },\n);\nDefaultAddToCheckButton.displayName = \"DefaultAddToCheckButton\";\n\n/**\n * Run status and date display component\n */\nconst RunStatusAndDateDisplay = memo(({ run }: { run: Run }) => {\n const statusText =\n run.status || (run.result ? \"Finished\" : run.error ? \"Failed\" : \"unknown\");\n\n // Determine color based on status\n const getStatusColor = (status: string) => {\n switch (status.toLowerCase()) {\n case \"finished\":\n return \"success.main\";\n case \"failed\":\n return \"error.main\";\n case \"running\":\n return \"primary.main\";\n case \"cancelled\":\n default:\n return \"text.secondary\";\n }\n };\n\n const relativeTime = run.run_at\n ? formatDistanceToNow(new Date(run.run_at), { addSuffix: true })\n : \"Unknown time\";\n\n return (\n <Typography variant=\"body2\" sx={{ color: \"text.secondary\" }}>\n <Box\n component=\"span\"\n sx={{ color: getStatusColor(statusText) }}\n fontWeight={600}\n >\n {statusText}\n </Box>\n {\"・\"}\n {relativeTime}\n </Typography>\n );\n});\nRunStatusAndDateDisplay.displayName = \"RunStatusAndDateDisplay\";\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\n/**\n * RunResultPane Component\n *\n * A reusable component for displaying run results with tabs for Result, Params, and Query.\n * Uses dependency injection for OSS-specific behaviors like tracking, sharing, and checklist.\n *\n * @example Basic usage with run data\n * ```tsx\n * import { RunResultPane } from '@datarecce/ui/components/run';\n *\n * function MyRunView({ run }) {\n * return (\n * <RunResultPane\n * run={run}\n * runId={run.run_id}\n * onClose={() => handleClose()}\n * onRerun={() => handleRerun()}\n * RunResultView={MyResultView}\n * />\n * );\n * }\n * ```\n *\n * @example With OSS-specific injections\n * ```tsx\n * <RunResultPane\n * run={run}\n * runId={run.run_id}\n * onCopyAsImage={handleCopyAsImage}\n * csvExport={{ canExportCSV: true, copyAsCSV, downloadAsCSV }}\n * onShareToCloud={handleShare}\n * onTrackCopyToClipboard={(type, from) => trackCopyToClipboard({ type, from })}\n * onAddToChecklist={handleAddToChecklist}\n * authed={isAuthenticated}\n * RunResultView={QueryResultView}\n * />\n * ```\n */\nfunction RunResultPaneComponent<VO = unknown, RefType = unknown>({\n // Core data\n runId,\n run,\n isRunning,\n error,\n\n // View configuration\n viewOptions,\n onViewOptionsChanged,\n isSingleEnvironment,\n\n // Feature toggles\n disableDatabaseQuery,\n disableShare,\n disableUpdateChecklist,\n\n // Event handlers\n onClose,\n onCancel: _onCancel,\n onRerun,\n\n // Export/Share handlers\n onCopyAsImage,\n onCopyMouseEnter,\n onCopyMouseLeave,\n csvExport,\n authed,\n onShareToCloud,\n onShowAuthModal,\n onTrackCopyToClipboard,\n\n // Checklist handlers\n onGoToCheck,\n onAddToChecklist,\n\n // Custom components\n SingleEnvironmentNotification,\n SqlEditorComponent,\n DualSqlEditorComponent,\n AuthModalComponent,\n RunResultView,\n resultViewRef,\n\n // Children\n children,\n}: RunResultPaneProps<VO, RefType>) {\n const isDark = useIsDark();\n const [tabValue, setTabValue] = useState<RunResultPaneTabValue>(\"result\");\n const [showSingleEnvNotification, setShowSingleEnvNotification] =\n useState(true);\n const [showAuthModal, setShowAuthModal] = useState(false);\n\n const isQuery =\n run?.type === \"query\" ||\n run?.type === \"query_diff\" ||\n run?.type === \"query_base\";\n\n const disableCopyToClipboard =\n !runId || !run?.result || !!error || tabValue !== \"result\";\n\n const handleCopyAsImage = useCallback(async () => {\n await onCopyAsImage?.();\n if (onTrackCopyToClipboard) {\n onTrackCopyToClipboard(run?.type ?? \"unknown\", \"run\");\n }\n }, [onCopyAsImage, onTrackCopyToClipboard, run?.type]);\n\n const handleShowAuthModal = useCallback(() => {\n if (onShowAuthModal) {\n onShowAuthModal();\n } else {\n setShowAuthModal(true);\n }\n }, [onShowAuthModal]);\n\n // Determine if we should show query tab content\n const isQueryDiff = run?.type === \"query_diff\";\n const queryParams = run?.params as\n | { sql_template?: string; base_sql_template?: string }\n | undefined;\n\n return (\n <Box\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n height: \"100%\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n }}\n >\n {/* Single environment notification */}\n {isSingleEnvironment &&\n showSingleEnvNotification &&\n SingleEnvironmentNotification && (\n <SingleEnvironmentNotification\n runType={run?.type}\n onClose={() => setShowSingleEnvNotification(false)}\n />\n )}\n\n {/* Header with tabs and actions */}\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n borderBottom: 1,\n borderColor: \"divider\",\n mb: \"1px\",\n }}\n >\n <Tabs\n value={tabValue}\n onChange={(_, newValue) =>\n setTabValue(newValue as RunResultPaneTabValue)\n }\n >\n <Tab label=\"Result\" value=\"result\" />\n <Tab label=\"Params\" value=\"params\" />\n {isQuery && <Tab label=\"Query\" value=\"query\" />}\n </Tabs>\n <Box sx={{ flexGrow: 1 }} />\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ overflow: \"hidden\", pr: 1 }}\n alignItems=\"center\"\n >\n {run && <RunStatusAndDateDisplay run={run} />}\n <Button\n variant=\"outlined\"\n color=\"neutral\"\n disabled={!runId || isRunning || disableDatabaseQuery}\n size=\"small\"\n onClick={onRerun}\n startIcon={<PiRepeat />}\n sx={{ textTransform: \"none\" }}\n >\n Rerun\n </Button>\n\n {/* Export or Share menu */}\n {disableShare ? (\n <DefaultExportMenu\n run={run}\n disableExport={disableCopyToClipboard}\n onCopyAsImage={handleCopyAsImage}\n onMouseEnter={onCopyMouseEnter}\n onMouseLeave={onCopyMouseLeave}\n csvExport={csvExport}\n />\n ) : (\n <DefaultShareMenu\n run={run}\n disableExport={disableCopyToClipboard}\n onCopyAsImage={handleCopyAsImage}\n onMouseEnter={onCopyMouseEnter}\n onMouseLeave={onCopyMouseLeave}\n csvExport={csvExport}\n authed={authed}\n onShareToCloud={onShareToCloud}\n onShowAuthModal={handleShowAuthModal}\n />\n )}\n\n {/* Add to Check button */}\n <DefaultAddToCheckButton\n runId={runId}\n run={run}\n disableUpdateChecklist={disableUpdateChecklist}\n hasError={!!error}\n onGoToCheck={onGoToCheck}\n onAddToChecklist={onAddToChecklist}\n />\n\n {/* Close button */}\n <IconButton size=\"small\" onClick={onClose}>\n <IoClose />\n </IconButton>\n </Stack>\n </Box>\n\n {/* Tab content */}\n {tabValue === \"result\" && (RunResultView || children) && (\n <RunView\n ref={resultViewRef}\n isRunning={isRunning}\n error={error}\n run={run}\n onCancel={_onCancel}\n viewOptions={viewOptions}\n onViewOptionsChanged={onViewOptionsChanged}\n RunResultView={RunResultView}\n >\n {children}\n </RunView>\n )}\n\n {tabValue === \"params\" && run && (\n <ParamView type={run.type} params={run.params} />\n )}\n\n {tabValue === \"query\" && run && isQuery && queryParams?.sql_template && (\n <>\n {isQueryDiff && DualSqlEditorComponent ? (\n <DualSqlEditorComponent\n value={queryParams.sql_template}\n baseValue={queryParams.base_sql_template}\n readOnly={true}\n />\n ) : SqlEditorComponent ? (\n <SqlEditorComponent\n value={queryParams.sql_template}\n readOnly={true}\n />\n ) : (\n <CodeEditor\n value={queryParams.sql_template}\n language=\"sql\"\n readOnly={true}\n theme=\"dark\"\n height=\"100%\"\n />\n )}\n </>\n )}\n\n {/* Auth modal */}\n {AuthModalComponent && showAuthModal && (\n <AuthModalComponent\n open={showAuthModal}\n onClose={() => setShowAuthModal(false)}\n />\n )}\n </Box>\n );\n}\n\nexport const RunResultPane = memo(RunResultPaneComponent) as <\n VO = unknown,\n RefType = unknown,\n>(\n props: RunResultPaneProps<VO, RefType>,\n) => ReturnType<typeof RunResultPaneComponent>;\n\n// Add display name for debugging\n(RunResultPane as { displayName?: string }).displayName = \"RunResultPane\";\n","import Box from \"@mui/material/Box\";\nimport IconButton from \"@mui/material/IconButton\";\nimport MuiLink from \"@mui/material/Link\";\nimport { PropsWithChildren } from \"react\";\nimport { FiInfo } from \"react-icons/fi\";\nimport { IoClose } from \"react-icons/io5\";\nimport { LuExternalLink } from \"react-icons/lu\";\n\nexport const RecceNotification = (\n props: PropsWithChildren<{\n onClose: () => void;\n align?: string;\n }>,\n) => {\n return (\n <Box\n sx={{\n display: \"flex\",\n flex: 1,\n minHeight: \"48px\",\n m: \"4px\",\n px: \"16px\",\n py: \"12px\",\n bgcolor: \"primary.50\",\n border: \"1px solid\",\n borderRadius: \"4px\",\n borderColor: \"primary.400\",\n alignItems: props.align ?? \"center\",\n gap: \"12px\",\n }}\n >\n <Box\n component={FiInfo}\n sx={{ width: \"20px\", height: \"20px\", color: \"primary.900\" }}\n />\n {props.children}\n <Box sx={{ flexGrow: 1 }} />\n <IconButton size=\"small\" onClick={props.onClose}>\n <IoClose />\n </IconButton>\n </Box>\n );\n};\n\nexport const LearnHowLink = () => {\n return (\n <MuiLink\n href=\"https://docs.reccehq.com/get-started/#prepare-dbt-artifacts\"\n target=\"_blank\"\n sx={{\n color: \"primary.main\",\n fontWeight: \"bold\",\n textDecoration: \"underline\",\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 0.5,\n }}\n >\n Learn how <LuExternalLink />\n </MuiLink>\n );\n};\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Stack from \"@mui/material/Stack\";\nimport { alpha } from \"@mui/material/styles\";\nimport Typography from \"@mui/material/Typography\";\nimport React, { useMemo } from \"react\";\nimport { FaPlay } from \"react-icons/fa6\";\nimport type { ManifestMetadata } from \"../../api\";\nimport {\n useLineageGraphContext,\n useRecceInstanceContext,\n} from \"../../contexts\";\nimport { useIsDark } from \"../../hooks\";\nimport { colors } from \"../../theme\";\nimport { extractSchemas, formatTimeToNow } from \"../../utils\";\nimport { CodeEditor } from \"../editor/CodeEditor\";\n\nexport interface SqlEditorProps {\n language?: string;\n theme?: string;\n value: string;\n baseValue?: string;\n onChange?: (value: string) => void;\n onChangeBase?: (value: string) => void;\n onRun?: () => void;\n onRunBase?: () => void;\n onRunDiff?: () => void;\n options?: {\n readOnly?: boolean;\n fontSize?: number;\n lineNumbers?: \"on\" | \"off\";\n wordWrap?: \"on\" | \"off\";\n };\n manifestData?: ManifestMetadata;\n schemas?: string;\n label?: string;\n CustomEditor?: React.ReactNode;\n}\n\nexport interface DualSqlEditorProps extends SqlEditorProps {\n labels?: [string, string]; // [baseLabel, currentLabel]\n SetupGuide?: React.ReactNode;\n}\n\nfunction SqlEditor({\n value,\n onChange,\n onRun,\n onRunBase,\n onRunDiff,\n label,\n CustomEditor,\n options = {},\n manifestData,\n schemas,\n ...props\n}: SqlEditorProps) {\n const { featureToggles } = useRecceInstanceContext();\n const isDark = useIsDark();\n\n const handleEditorChange = (value: string) => {\n if (onChange) {\n onChange(value);\n }\n };\n let timestamp = \"\";\n if (manifestData) {\n timestamp = manifestData.generated_at\n ? formatTimeToNow(manifestData.generated_at)\n : \"\";\n }\n\n // Convert keyboard shortcuts to CodeMirror format\n const keyBindings = useMemo(() => {\n const bindings = [];\n\n if (onRun) {\n bindings.push({\n key: \"Mod-Enter\", // Ctrl/Cmd + Enter\n run: () => {\n onRun();\n return true;\n },\n });\n }\n\n if (onRunBase) {\n bindings.push({\n key: \"Alt-Enter\",\n run: () => {\n onRunBase();\n return true;\n },\n });\n }\n\n if (onRunDiff) {\n bindings.push({\n key: \"Mod-Shift-Enter\", // Ctrl/Cmd + Shift + Enter\n run: () => {\n onRunDiff();\n return true;\n },\n });\n }\n\n return bindings;\n }, [onRun, onRunBase, onRunDiff]);\n\n // ... header rendering stays the same ...\n\n return (\n <>\n {(label ?? onRun ?? onRunBase) && (\n <Stack\n direction=\"row\"\n sx={{\n bgcolor: isDark\n ? alpha(colors.neutral[600], 0.7)\n : alpha(colors.neutral[200], 0.7),\n height: \"40px\",\n minHeight: \"40px\",\n fontSize: \"14px\",\n alignItems: \"center\",\n m: 0,\n p: \"0px 16px\",\n flex: \"0 0 40px\",\n }}\n >\n <Typography\n component=\"strong\"\n sx={{ fontWeight: \"bold\" }}\n className=\"no-track-pii-safe\"\n >\n {label ? label.toUpperCase() : \"\"}\n </Typography>\n {manifestData && (\n <span className=\"ml-1\">\n (\n {schemas && (\n <span className=\"no-track-pii-safe\">{schemas}, </span>\n )}\n <span>{timestamp}</span>)\n </span>\n )}\n\n <Box sx={{ flexGrow: 1 }} />\n {(onRun ?? onRunBase) && (\n <Button\n size=\"xsmall\"\n variant=\"outlined\"\n onClick={onRun ?? onRunBase}\n sx={{ bgcolor: \"background.paper\", p: \"6px 12px\" }}\n disabled={featureToggles.disableDatabaseQuery}\n startIcon={<FaPlay />}\n >\n Run Query\n </Button>\n )}\n </Stack>\n )}\n {CustomEditor ?? (\n <CodeEditor\n value={value}\n onChange={handleEditorChange}\n language=\"sql\"\n readOnly={options.readOnly ?? false}\n lineNumbers={options.lineNumbers !== \"off\"}\n wordWrap={options.wordWrap !== \"off\"}\n fontSize={options.fontSize ?? 16}\n keyBindings={keyBindings}\n theme={isDark ? \"dark\" : \"light\"}\n className=\"no-track-pii-safe max-h-dvh h-full\"\n />\n )}\n </>\n );\n}\n\nexport function DualSqlEditor({\n value,\n baseValue,\n onChange,\n onChangeBase,\n onRun,\n onRunBase,\n onRunDiff,\n options = {},\n labels,\n SetupGuide,\n ...props\n}: DualSqlEditorProps) {\n const baseLabel = labels ? labels[0] : \"Base\";\n const currentLabel = labels ? labels[1] : \"Current\";\n const { envInfo, lineageGraph } = useLineageGraphContext();\n\n let dbtBase: ManifestMetadata | undefined;\n let dbtCurrent: ManifestMetadata | undefined;\n if (envInfo?.dbt?.base && envInfo.dbt.current) {\n dbtBase = envInfo.dbt.base;\n dbtCurrent = envInfo.dbt.current;\n }\n\n const [baseSchemas, currentSchemas] = extractSchemas(lineageGraph);\n\n return (\n <>\n <Stack direction=\"row\" sx={{ height: \"100%\", gap: 0 }}>\n <Stack\n sx={{\n height: \"100%\",\n width: \"50%\",\n gap: 0,\n borderRight: \"1px solid\",\n borderRightColor: \"divider\",\n }}\n >\n <SqlEditor\n label={baseLabel}\n value={baseValue ?? \"\"}\n onChange={onChangeBase}\n onRunBase={onRunBase}\n options={options}\n CustomEditor={SetupGuide}\n manifestData={dbtBase ?? undefined}\n schemas={Array.from(baseSchemas).join(\", \")}\n {...props}\n />\n </Stack>\n <Stack sx={{ height: \"100%\", width: \"50%\", gap: 0 }}>\n <SqlEditor\n label={currentLabel}\n value={value}\n onChange={onChange}\n onRun={onRun}\n options={options}\n manifestData={dbtCurrent ?? undefined}\n schemas={Array.from(currentSchemas).join(\", \")}\n {...props}\n />\n </Stack>\n </Stack>\n </>\n );\n}\n\nexport default SqlEditor;\n","\"use client\";\n\n/**\n * @file run/RunResultPaneOss.tsx\n * @description OSS wrapper for RunResultPane component.\n *\n * This thin wrapper:\n * 1. Imports the base component from @datarecce/ui\n * 2. Injects OSS-specific context and behavior (tracking, clipboard, API client)\n *\n * OSS-specific behaviors injected:\n * - Analytics tracking (Amplitude via trackCopyToClipboard, trackShareState)\n * - Share state context (RecceShareStateContext)\n * - API client configuration (useApiConfig)\n * - Screenshot/clipboard functionality (useCopyToClipboardButton)\n * - CSV export functionality (useCSVExport)\n * - Run management (useRun hook)\n * - Navigation (useRouter)\n */\n\nimport Typography from \"@mui/material/Typography\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useRouter } from \"next/navigation\";\nimport { type Ref, useCallback, useState } from \"react\";\nimport {\n type AxiosQueryParams,\n cacheKeys,\n createCheckByRun,\n runTypeHasRef,\n} from \"../../api\";\nimport {\n useRecceActionContext,\n useRecceInstanceContext,\n useRouteConfig,\n} from \"../../contexts\";\nimport {\n useApiConfig,\n useCopyToClipboardButton,\n useCSVExport,\n useRecceShareStateContext,\n useRun,\n} from \"../../hooks\";\nimport { trackCopyToClipboard, trackShareState } from \"../../lib/api/track\";\nimport AuthModal from \"../app/AuthModal\";\nimport { LearnHowLink, RecceNotification } from \"../onboarding-guide\";\nimport { DualSqlEditor, SqlEditor } from \"../query\";\nimport { RunResultPane as BaseRunResultPane } from \"./RunResultPane\";\nimport { findByRunType } from \"./registry\";\nimport { RefTypes, RegistryEntry, ViewOptionTypes } from \"./types\";\n\n// ============================================================================\n// OSS Props Interface\n// ============================================================================\n\ninterface RunPageProps {\n onClose?: () => void;\n disableAddToChecklist?: boolean;\n isSingleEnvironment?: boolean;\n}\n\n// ============================================================================\n// Single Environment Notification Component (OSS-specific)\n// ============================================================================\n\nconst SingleEnvironmentSetupNotification = ({\n runType,\n onClose,\n}: {\n runType?: string;\n onClose: () => void;\n}) => {\n switch (runType) {\n case \"row_count\":\n return (\n <RecceNotification onClose={onClose}>\n <Typography>\n Enable row count diffing, and other Recce features, by configuring a\n base dbt environment to compare against. <LearnHowLink />\n </Typography>\n </RecceNotification>\n );\n case \"profile\":\n return (\n <RecceNotification onClose={onClose}>\n <Typography>\n Enable data-profile diffing, and other Recce features, by\n configuring a base dbt environment to compare against.{\" \"}\n <LearnHowLink />\n </Typography>\n </RecceNotification>\n );\n default:\n return null;\n }\n};\n\n// ============================================================================\n// SQL Editor Adapter Components\n// ============================================================================\n\ninterface SqlEditorAdapterProps {\n value: string;\n baseValue?: string;\n readOnly?: boolean;\n}\n\nconst SqlEditorAdapter = ({ value, readOnly }: SqlEditorAdapterProps) => (\n <SqlEditor value={value} options={{ readOnly }} />\n);\n\nconst DualSqlEditorAdapter = ({\n value,\n baseValue,\n readOnly,\n}: SqlEditorAdapterProps) => (\n <DualSqlEditor value={value} baseValue={baseValue} options={{ readOnly }} />\n);\n\n// ============================================================================\n// Auth Modal Adapter Component\n// ============================================================================\n\nconst AuthModalAdapter = ({\n open,\n onClose,\n}: {\n open: boolean;\n onClose: () => void;\n}) => (\n <AuthModal\n parentOpen={open}\n handleParentClose={() => onClose()}\n ignoreCookie\n variant=\"enable-share\"\n />\n);\n\n// ============================================================================\n// PrivateLoadableRunView - Main Implementation (OSS Wrapper)\n// ============================================================================\n\n/**\n * OSS implementation that loads run data and injects OSS-specific behavior\n * into the RunResultPane component.\n */\nexport const PrivateLoadableRunView = ({\n runId,\n onClose,\n isSingleEnvironment,\n}: {\n runId?: string;\n onClose?: () => void;\n isSingleEnvironment?: boolean;\n}) => {\n const { featureToggles, authed } = useRecceInstanceContext();\n const { runAction } = useRecceActionContext();\n const { error, run, onCancel, isRunning } = useRun(runId);\n const [viewOptions, setViewOptions] = useState<ViewOptionTypes>();\n const queryClient = useQueryClient();\n const router = useRouter();\n const { apiClient } = useApiConfig();\n const { basePath } = useRouteConfig();\n const { handleShareClick } = useRecceShareStateContext();\n\n // Get the result view component from registry\n let RunResultView: RegistryEntry[\"RunResultView\"] | undefined;\n if (run && runTypeHasRef(run.type)) {\n RunResultView = findByRunType(run.type)\n .RunResultView as RegistryEntry[\"RunResultView\"];\n }\n\n // Copy to clipboard functionality\n const { ref, onCopyToClipboard, onMouseEnter, onMouseLeave } =\n useCopyToClipboardButton();\n\n // CSV export functionality\n const csvExport = useCSVExport({\n run,\n viewOptions: viewOptions as Record<string, unknown>,\n });\n\n // Rerun handler\n const handleRerun = useCallback(() => {\n if (run) {\n runAction(run.type, run.params as unknown as AxiosQueryParams);\n }\n }, [run, runAction]);\n\n // Share to cloud handler\n const handleShareToCloud = useCallback(async () => {\n await handleShareClick();\n trackShareState({ name: \"create\" });\n }, [handleShareClick]);\n\n // Copy to clipboard handler with tracking\n const handleCopyAsImage = useCallback(async () => {\n await onCopyToClipboard();\n trackCopyToClipboard({\n type: run?.type ?? \"unknown\",\n from: \"run\",\n });\n }, [onCopyToClipboard, run?.type]);\n\n // Go to check handler\n const handleGoToCheck = useCallback(\n (checkId: string) => {\n router.push(`${basePath}/checks/?id=${checkId}`);\n },\n [router.push, basePath],\n );\n\n // Add to checklist handler\n const handleAddToChecklist = useCallback(async () => {\n if (!runId) {\n return;\n }\n const check = await createCheckByRun(\n runId,\n viewOptions as Record<string, unknown>,\n apiClient,\n );\n await queryClient.invalidateQueries({ queryKey: cacheKeys.checks() });\n router.push(`${basePath}/checks/?id=${check.check_id}`);\n }, [runId, viewOptions, apiClient, queryClient, router.push, basePath]);\n\n return (\n <BaseRunResultPane\n // Core data\n runId={runId}\n run={run}\n isRunning={isRunning}\n error={error}\n // View configuration\n viewOptions={viewOptions}\n onViewOptionsChanged={setViewOptions}\n isSingleEnvironment={isSingleEnvironment}\n // Feature toggles\n disableDatabaseQuery={featureToggles.disableDatabaseQuery}\n disableShare={featureToggles.disableShare}\n disableUpdateChecklist={featureToggles.disableUpdateChecklist}\n // Event handlers\n onClose={onClose}\n onCancel={onCancel}\n onRerun={handleRerun}\n // Export/Share handlers\n onCopyAsImage={handleCopyAsImage}\n onCopyMouseEnter={onMouseEnter}\n onCopyMouseLeave={onMouseLeave}\n csvExport={csvExport}\n authed={authed}\n onShareToCloud={handleShareToCloud}\n // Checklist handlers\n onGoToCheck={handleGoToCheck}\n onAddToChecklist={handleAddToChecklist}\n // Custom components (OSS-specific)\n SingleEnvironmentNotification={SingleEnvironmentSetupNotification}\n SqlEditorComponent={SqlEditorAdapter}\n DualSqlEditorComponent={DualSqlEditorAdapter}\n AuthModalComponent={AuthModalAdapter}\n RunResultView={RunResultView}\n resultViewRef={ref as Ref<RefTypes>}\n />\n );\n};\n\n// ============================================================================\n// RunResultPaneOss - Public Component (OSS Wrapper)\n// ============================================================================\n\n/**\n * OSS RunResultPane Component\n *\n * A thin wrapper around RunResultPane that injects OSS-specific\n * context and behavior including:\n * - Analytics tracking (Amplitude)\n * - Share state context\n * - API client configuration\n * - Screenshot/clipboard functionality\n * - CSV export functionality\n *\n * @example\n * ```tsx\n * import { RunResultPaneOss } from \"@datarecce/ui/components/run\";\n *\n * function MyRunView() {\n * return (\n * <RunResultPaneOss\n * onClose={() => handleClose()}\n * isSingleEnvironment={isSingleEnv}\n * />\n * );\n * }\n * ```\n */\nexport const RunResultPaneOss = ({\n onClose,\n isSingleEnvironment,\n}: RunPageProps) => {\n const { runId } = useRecceActionContext();\n\n return (\n <PrivateLoadableRunView\n runId={runId}\n onClose={onClose}\n isSingleEnvironment={isSingleEnvironment}\n />\n );\n};\n","import Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Typography from \"@mui/material/Typography\";\nimport {\n FallbackRender,\n ErrorBoundary as SentryErrorBoundary,\n} from \"@sentry/react\";\nimport * as React from \"react\";\nimport { ReactNode } from \"react\";\nimport { useThemeColors } from \"../../hooks\";\n\n/**\n * Fallback component that renders when an error is caught.\n * Uses useThemeColors for theme-aware styling.\n */\nfunction FallbackComponent({\n error,\n resetError,\n}: {\n error: Error;\n resetError: () => void;\n}) {\n const { background, text } = useThemeColors();\n\n return (\n <Box\n sx={{\n height: \"100%\",\n bgcolor: background.subtle,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Box\n sx={{\n p: 4,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-start\",\n bgcolor: \"background.paper\",\n border: \"solid 1px\",\n borderColor: \"divider\",\n minHeight: \"200px\",\n }}\n >\n <Typography variant=\"h6\" sx={{ width: \"800px\" }}>\n You have encountered an error\n </Typography>\n\n <Box sx={{ flex: 1, fontSize: \"10pt\", color: text.secondary }}>\n {String(error)}\n </Box>\n\n <Button\n sx={{\n justifySelf: \"center\",\n alignSelf: \"center\",\n mt: \"20px\",\n }}\n color=\"iochmara\"\n variant=\"contained\"\n size=\"small\"\n onClick={resetError}\n >\n Reset\n </Button>\n </Box>\n </Box>\n );\n}\n\n/**\n * Wrapper function that converts the component to Sentry's FallbackRender format.\n */\nconst Fallback: FallbackRender = (errorData) => {\n return (\n <FallbackComponent\n error={errorData.error as Error}\n resetError={errorData.resetError}\n />\n );\n};\n\nexport const ErrorBoundary = ({\n children,\n fallback = Fallback,\n}: {\n children: ReactNode;\n fallback?: React.ReactElement | FallbackRender | undefined;\n}) => {\n return (\n <SentryErrorBoundary fallback={fallback}>{children}</SentryErrorBoundary>\n );\n};\n","\"use client\";\n\n/**\n * @file run/RunViewOss.tsx\n * @description OSS wrapper for RunView component with Sentry error boundary injection.\n *\n * This thin wrapper imports the base RunView from @datarecce/ui and injects\n * OSS-specific dependencies:\n * - ErrorBoundary: Sentry error boundary for error tracking\n * - ResultErrorFallback: Error fallback component for run results\n *\n * @example\n * ```tsx\n * import { RunViewOss } from \"@datarecce/ui/components/run\";\n * import { QueryResultView } from \"./QueryResultView\";\n *\n * function MyComponent() {\n * return (\n * <RunViewOss\n * run={run}\n * isRunning={isRunning}\n * RunResultView={QueryResultView}\n * onCancel={handleCancel}\n * />\n * );\n * }\n * ```\n */\n\nimport type { ReactNode, Ref } from \"react\";\nimport { forwardRef } from \"react\";\nimport type { Run } from \"../../api\";\nimport ResultErrorFallback from \"../../lib/result/ResultErrorFallback\";\nimport { ErrorBoundary } from \"../errorboundary\";\nimport {\n RunView as BaseRunView,\n type RunViewProps as BaseRunViewProps,\n} from \"./RunView\";\nimport {\n RefTypes,\n RegistryEntry,\n RunResultViewProps,\n ViewOptionTypes,\n} from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * OSS-specific RunView props using OSS types for backward compatibility.\n *\n * @typeParam VO - View options type (defaults to ViewOptionTypes union)\n */\nexport interface RunViewOssProps<VO = ViewOptionTypes> {\n /** Whether a run is currently executing */\n isRunning?: boolean;\n\n /** The run object containing execution state and results */\n run?: Run;\n\n /** Error that occurred during run execution */\n error?: Error | null;\n\n /** Progress information for the current run */\n progress?: Run[\"progress\"];\n\n /** Whether the run is being aborted */\n isAborting?: boolean;\n\n /**\n * Whether this is a check detail view.\n * @deprecated This prop may be removed in future versions.\n */\n isCheckDetail?: boolean;\n\n /** Callback when user cancels the run */\n onCancel?: () => void;\n\n /** Callback to execute/re-execute the run */\n onExecuteRun?: () => void;\n\n /** Current view options for result display */\n viewOptions?: VO;\n\n /** Callback when view options change */\n onViewOptionsChanged?: (viewOptions: VO) => void;\n\n /**\n * Component to render run results.\n * Either RunResultView or children is required.\n */\n RunResultView?: RegistryEntry[\"RunResultView\"];\n\n /**\n * Render prop for custom result rendering.\n * Either RunResultView or children is required.\n */\n children?: (params: RunResultViewProps<ViewOptionTypes>) => ReactNode;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * OSS RunView component with Sentry error boundary pre-injected.\n *\n * This is a thin wrapper around the base RunView from @datarecce/ui that\n * injects OSS-specific error handling via Sentry.\n *\n * States:\n * 1. **Error state**: Shows error message from API response or run.error\n * 2. **Running state**: Shows loading spinner with progress and cancel button\n * 3. **Loading state**: Shows spinner when run is undefined\n * 4. **Result state**: Renders RunResultView or children with run results,\n * wrapped in Sentry error boundary for crash reporting\n *\n * @example\n * ```tsx\n * const ref = useRef<DataGridHandle>(null);\n *\n * <RunViewOss\n * ref={ref}\n * run={run}\n * RunResultView={QueryResultView}\n * viewOptions={viewOptions}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const RunViewOss = forwardRef<RefTypes, RunViewOssProps>(\n function RunViewOss(props, ref) {\n // Cast to base props for compatibility\n const baseProps: BaseRunViewProps = {\n ...props,\n ErrorBoundary,\n errorBoundaryFallback: ResultErrorFallback,\n };\n\n return <BaseRunView {...baseProps} ref={ref as Ref<unknown>} />;\n },\n);\n\n// Set display name for debugging\nRunViewOss.displayName = \"RunViewOss\";\n","/**\n * @file useMultiNodesAction.ts\n * @description Hook for executing batch operations on multiple lineage graph nodes.\n *\n * This hook provides functionality for running data validation operations on\n * selected nodes in the lineage graph. It supports two execution modes:\n *\n * 1. **multi_nodes mode** - A single run is submitted for all nodes at once\n * (used for row_count, row_count_diff)\n *\n * 2. **per_node mode** - Individual runs are submitted sequentially per node\n * (used for value_diff which requires primary key per model)\n *\n * The hook manages action state, handles run polling, cancellation, and provides\n * callbacks for UI updates during execution.\n *\n * @example\n * ```tsx\n * const { actionState, runRowCount, runRowCountDiff, cancel, reset } = useMultiNodesAction(\n * selectedNodes,\n * {\n * onActionStarted: () => setIsRunning(true),\n * onActionNodeUpdated: (node) => updateNodeUI(node),\n * onActionCompleted: () => setIsRunning(false),\n * }\n * );\n * ```\n */\n\nimport { useRef } from \"react\";\nimport {\n type Check,\n cancelRun,\n createLineageDiffCheck,\n createSchemaDiffCheck,\n type RowCountDiffParams,\n type RowCountParams,\n type Run,\n type RunType,\n submitRun,\n type ValueDiffParams,\n waitRun,\n} from \"../api\";\nimport { useRecceActionContext } from \"../contexts/action\";\nimport type { ActionState, LineageGraphNode } from \"../contexts/lineage/types\";\nimport { useApiClient } from \"../providers\";\n\n/**\n * Action types that can be tracked.\n * Maps to common data validation operations.\n */\nexport type MultiNodesActionType =\n | \"row_count\"\n | \"row_count_diff\"\n | \"value_diff\";\n\n/**\n * Properties passed to the tracking callback when an action is executed.\n */\nexport interface MultiNodesActionTrackProps {\n /** The type of action being executed */\n action: MultiNodesActionType;\n /** Source/location where the action was triggered */\n source: string;\n /** Number of nodes included in the action */\n node_count: number;\n}\n\n/**\n * Callbacks for tracking analytics events during action execution.\n * This is intentionally optional to allow the base hook to be used\n * in environments without analytics (e.g., embedded or library usage).\n */\nexport interface MultiNodesActionTracking {\n /**\n * Called when an action is executed.\n * Implementations can use this to send analytics events.\n * @param props - Properties describing the action\n */\n onTrackAction?: (props: MultiNodesActionTrackProps) => void;\n}\n\n/**\n * Lifecycle callbacks for action execution.\n * These are called at key points during action execution to allow\n * the UI to respond to state changes.\n */\nexport interface MultiNodesActionCallbacks {\n /** Called when any action starts executing */\n onActionStarted: () => void;\n /** Called when a node's action state is updated (status change, completion, etc.) */\n onActionNodeUpdated: (node: LineageGraphNode) => void;\n /** Called when all actions in the batch complete (success, failure, or cancellation) */\n onActionCompleted: () => void;\n}\n\n/**\n * Configuration options for the useMultiNodesAction hook.\n */\nexport interface UseMultiNodesActionOptions\n extends MultiNodesActionCallbacks,\n MultiNodesActionTracking {\n /**\n * Source identifier for tracking where actions are triggered from.\n * Used in analytics events.\n * @default \"lineage_view_top_bar\"\n */\n trackingSource?: string;\n}\n\n/**\n * Return type for the useMultiNodesAction hook.\n */\nexport interface UseMultiNodesActionReturn {\n /** Current action state (mutable ref for performance) */\n actionState: ActionState;\n /** Execute row count on selected nodes (multi_nodes mode) */\n runRowCount: () => Promise<void>;\n /** Execute row count diff on selected nodes (multi_nodes mode) */\n runRowCountDiff: () => Promise<void>;\n /** Execute value diff on selected nodes (per_node mode) */\n runValueDiff: () => Promise<void>;\n /** Create a lineage diff check for selected nodes */\n addLineageDiffCheck: () => Promise<Check>;\n /** Create a schema diff check for selected nodes */\n addSchemaDiffCheck: () => Promise<Check>;\n /** Cancel the current running action */\n cancel: () => Promise<void>;\n /** Reset action state to initial values */\n reset: () => void;\n}\n\n/**\n * Initial state for action state object.\n * This is reset when the hook initializes or reset() is called.\n */\nconst initValue: ActionState = {\n mode: \"per_node\",\n status: \"pending\",\n completed: 0,\n total: 0,\n actions: {},\n};\n\n/**\n * Hook for executing batch operations on multiple lineage graph nodes.\n *\n * Provides methods to run data validation operations (row count, row count diff,\n * value diff) on selected nodes, with support for action tracking, cancellation,\n * and progress monitoring.\n *\n * @param nodes - Array of lineage graph nodes to operate on\n * @param options - Configuration options including callbacks and tracking\n * @returns Object containing action state and operation methods\n *\n * @example\n * ```tsx\n * function ActionBar({ selectedNodes }) {\n * const {\n * actionState,\n * runRowCount,\n * runRowCountDiff,\n * runValueDiff,\n * cancel,\n * reset\n * } = useMultiNodesAction(selectedNodes, {\n * onActionStarted: () => console.log('Started'),\n * onActionNodeUpdated: (node) => console.log('Updated:', node.id),\n * onActionCompleted: () => console.log('Completed'),\n * onTrackAction: (props) => analytics.track(props),\n * });\n *\n * return (\n * <div>\n * <button onClick={runRowCount}>Row Count</button>\n * <button onClick={cancel}>Cancel</button>\n * <span>{actionState.status}</span>\n * </div>\n * );\n * }\n * ```\n */\nexport const useMultiNodesAction = (\n nodes: LineageGraphNode[],\n options: UseMultiNodesActionOptions,\n): UseMultiNodesActionReturn => {\n const {\n onActionStarted,\n onActionNodeUpdated,\n onActionCompleted,\n onTrackAction,\n trackingSource = \"lineage_view_top_bar\",\n } = options;\n\n const apiClient = useApiClient();\n const actionState = useRef<ActionState>({\n ...initValue,\n }).current;\n\n const { showRunId } = useRecceActionContext();\n\n /**\n * Submit a single run for multiple nodes (multi_nodes mode).\n * Used by row_count and row_count_diff operations.\n */\n const submitRunForNodes = async (\n type: RunType,\n skip: (node: LineageGraphNode) => string | undefined,\n getParams: (\n nodes: LineageGraphNode[],\n ) => RowCountParams | RowCountDiffParams,\n ) => {\n actionState.mode = \"multi_nodes\";\n actionState.actions = {};\n const mode = actionState.mode;\n const actions = actionState.actions;\n\n onActionStarted();\n actionState.status = \"running\";\n\n const candidates: LineageGraphNode[] = [];\n\n for (const node of nodes) {\n const skipReason = skip(node);\n\n if (skipReason) {\n actions[node.id] = {\n mode,\n status: \"skipped\",\n skipReason,\n };\n onActionNodeUpdated(node);\n } else {\n actions[node.id] = { mode, status: \"pending\" };\n candidates.push(node);\n }\n }\n\n const params = getParams(candidates);\n\n try {\n const { run_id } = await submitRun(\n type,\n params,\n { nowait: true },\n apiClient,\n );\n showRunId(run_id);\n actionState.currentRun = { run_id };\n actionState.total = 1;\n\n for (;;) {\n const run = (await waitRun(run_id, 2, apiClient)) as Run;\n actionState.currentRun = run;\n\n const status = run.error\n ? \"failure\"\n : run.result\n ? \"success\"\n : \"running\";\n\n for (const node of candidates) {\n actions[node.id] = {\n mode,\n status,\n run,\n };\n onActionNodeUpdated(node);\n }\n\n if (run.error || run.result) {\n break;\n }\n }\n } catch (_e) {\n // Error handling is done via actionState, no need to throw\n }\n\n actionState.completed = 1;\n if ((actionState.status as string) === \"canceling\") {\n actionState.status = \"canceled\";\n onActionCompleted();\n return;\n }\n\n actionState.status = \"completed\";\n onActionCompleted();\n };\n\n /**\n * Submit individual runs for each node sequentially (per_node mode).\n * Used by value_diff operation which requires per-model primary keys.\n */\n const submitRunsPerNodes = async (\n type: RunType,\n getParams: (node: LineageGraphNode) => {\n params?: ValueDiffParams;\n skipReason?: string;\n },\n ) => {\n actionState.mode = \"per_node\";\n actionState.actions = {};\n const mode = actionState.mode;\n const actions = actionState.actions;\n\n onActionStarted();\n actionState.status = \"running\";\n\n for (const node of nodes) {\n actions[node.id] = { mode, status: \"pending\" };\n onActionNodeUpdated(node);\n }\n\n actionState.completed = 0;\n actionState.total = nodes.length;\n\n for (const node of nodes) {\n const { params, skipReason } = getParams(node);\n if (skipReason) {\n actions[node.id] = {\n mode,\n status: \"skipped\",\n skipReason,\n };\n onActionNodeUpdated(node);\n } else {\n try {\n const { run_id } = await submitRun(\n type,\n params,\n { nowait: true },\n apiClient,\n );\n actionState.currentRun = { run_id };\n actions[node.id] = {\n mode,\n status: \"running\",\n };\n onActionNodeUpdated(node);\n\n for (;;) {\n const run = (await waitRun(run_id, 2, apiClient)) as Run;\n actionState.currentRun = run;\n const status = run.error\n ? \"failure\"\n : run.result\n ? \"success\"\n : \"running\";\n actions[node.id] = {\n mode,\n status,\n run,\n };\n onActionNodeUpdated(node);\n\n if (run.error || run.result) {\n break;\n }\n }\n } catch (_e) {\n // Error handling is done via actionState\n } finally {\n actionState.currentRun = undefined;\n }\n }\n actionState.completed++;\n if ((actionState.status as string) === \"canceling\") {\n actionState.status = \"canceled\";\n onActionCompleted();\n return;\n }\n }\n\n actionState.status = \"completed\";\n onActionCompleted();\n };\n\n /**\n * Execute row count on all selected model nodes.\n * Non-model nodes are skipped.\n */\n const runRowCount = async () => {\n onTrackAction?.({\n action: \"row_count\",\n source: trackingSource,\n node_count: nodes.length,\n });\n\n // Pre-mark non-model nodes as skipped for immediate UI feedback\n for (const node of nodes) {\n if (node.data.resourceType !== \"model\") {\n actionState.actions[node.id] = {\n mode: \"multi_nodes\",\n status: \"skipped\",\n skipReason: \"Not a model\",\n };\n onActionNodeUpdated(node);\n }\n }\n\n const skip = (node: LineageGraphNode) => {\n if (node.data.resourceType !== \"model\") {\n return \"Not a model\";\n }\n };\n const getParams = (nodes: LineageGraphNode[]): RowCountParams => {\n return {\n node_names: nodes.map((node) => node.data.name),\n };\n };\n\n await submitRunForNodes(\"row_count\", skip, getParams);\n };\n\n /**\n * Execute row count diff on all selected model nodes.\n * Non-model nodes are skipped.\n */\n const runRowCountDiff = async () => {\n onTrackAction?.({\n action: \"row_count_diff\",\n source: trackingSource,\n node_count: nodes.length,\n });\n\n // Pre-mark non-model nodes as skipped for immediate UI feedback\n for (const node of nodes) {\n if (node.data.resourceType !== \"model\") {\n actionState.actions[node.id] = {\n mode: \"multi_nodes\",\n status: \"skipped\",\n skipReason: \"Not a model\",\n };\n onActionNodeUpdated(node);\n }\n }\n\n const skip = (node: LineageGraphNode) => {\n if (node.data.resourceType !== \"model\") {\n return \"Not a model\";\n }\n };\n const getParams = (nodes: LineageGraphNode[]): RowCountDiffParams => {\n return {\n node_names: nodes.map((node) => node.data.name),\n };\n };\n\n await submitRunForNodes(\"row_count_diff\", skip, getParams);\n };\n\n /**\n * Execute value diff on all selected nodes that have primary keys.\n * Nodes without primary keys are skipped.\n */\n const runValueDiff = async () => {\n onTrackAction?.({\n action: \"value_diff\",\n source: trackingSource,\n node_count: nodes.length,\n });\n\n await submitRunsPerNodes(\"value_diff\", (node) => {\n const primaryKey = node.data.data.current?.primary_key;\n if (!primaryKey) {\n return {\n skipReason:\n \"No primary key found. The first unique column is used as primary key.\",\n };\n }\n\n const params: ValueDiffParams = {\n model: node.data.name,\n primary_key: primaryKey,\n };\n\n return { params };\n });\n };\n\n /**\n * Create a lineage diff check for the selected nodes.\n * @returns The created check object\n */\n const addLineageDiffCheck = async () => {\n const nodeIds = nodes.map((node) => node.id);\n return await createLineageDiffCheck(\n {\n node_ids: nodeIds,\n },\n apiClient,\n );\n };\n\n /**\n * Create a schema diff check for the selected nodes.\n * Handles both single node and multi-node scenarios.\n * @returns The created check object\n */\n const addSchemaDiffCheck = async () => {\n let check;\n if (nodes.length === 1) {\n check = await createSchemaDiffCheck({ node_id: nodes[0].id }, apiClient);\n } else {\n const nodeIds = nodes.map((node) => node.id);\n check = await createSchemaDiffCheck({ node_id: nodeIds }, apiClient);\n }\n return check;\n };\n\n /**\n * Cancel the current running action.\n * Sets status to \"canceling\" and calls cancelRun API if a run is in progress.\n */\n const cancel = async () => {\n actionState.status = \"canceling\";\n if (actionState.currentRun?.run_id) {\n await cancelRun(actionState.currentRun.run_id, apiClient);\n }\n };\n\n /**\n * Reset action state to initial values.\n * Call this before starting a new action to clear previous state.\n */\n const reset = () => {\n Object.assign(actionState, initValue);\n };\n\n return {\n actionState,\n runRowCount,\n runRowCountDiff,\n runValueDiff,\n addLineageDiffCheck,\n addSchemaDiffCheck,\n cancel,\n reset,\n };\n};\n","\"use client\";\n\n/**\n * @file useValueDiffAlertDialog.tsx\n * @description Hook for displaying a value diff confirmation dialog.\n *\n * This hook provides a callback-based API for confirming value diff operations\n * on multiple nodes. The actual tracking/analytics can be injected via callbacks.\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Stack from \"@mui/material/Stack\";\nimport type { JSX } from \"react\";\nimport { useCallback, useRef, useState } from \"react\";\nimport { IoClose } from \"react-icons/io5\";\n\n/**\n * Options for useValueDiffAlertDialog hook\n */\nexport interface UseValueDiffAlertDialogOptions {\n /** Callback invoked when user confirms the value diff operation */\n onConfirm?: (nodeCount: number) => void;\n /** Callback invoked when user cancels the value diff operation */\n onCancel?: (nodeCount: number) => void;\n}\n\n/**\n * Return type for useValueDiffAlertDialog hook\n */\nexport interface UseValueDiffAlertDialogReturn {\n /** Function to trigger the confirmation dialog. Returns a promise that resolves to true if confirmed, false if cancelled. */\n confirm: (nodeCount: number) => Promise<boolean>;\n /** The dialog component to render in your component tree */\n AlertDialog: JSX.Element;\n}\n\n/**\n * Hook for displaying a confirmation dialog before executing value diff on multiple nodes.\n *\n * @param options - Optional callbacks for tracking/analytics\n * @returns Object containing `confirm` function and `AlertDialog` component\n *\n * @example Basic usage without tracking\n * ```tsx\n * import { useValueDiffAlertDialog } from '@datarecce/ui/hooks';\n *\n * function MyComponent() {\n * const { confirm, AlertDialog } = useValueDiffAlertDialog();\n *\n * const handleValueDiff = async () => {\n * const confirmed = await confirm(5); // 5 nodes\n * if (confirmed) {\n * // Execute value diff\n * }\n * };\n *\n * return (\n * <>\n * <button onClick={handleValueDiff}>Run Value Diff</button>\n * {AlertDialog}\n * </>\n * );\n * }\n * ```\n *\n * @example With tracking callbacks\n * ```tsx\n * import { useValueDiffAlertDialog } from '@datarecce/ui/hooks';\n *\n * function MyComponent() {\n * const { confirm, AlertDialog } = useValueDiffAlertDialog({\n * onConfirm: (nodeCount) => {\n * analytics.track('value_diff_confirmed', { nodeCount });\n * },\n * onCancel: (nodeCount) => {\n * analytics.track('value_diff_cancelled', { nodeCount });\n * },\n * });\n *\n * return (\n * <>\n * <button onClick={() => confirm(10)}>Run Value Diff</button>\n * {AlertDialog}\n * </>\n * );\n * }\n * ```\n */\nexport function useValueDiffAlertDialog(\n options?: UseValueDiffAlertDialogOptions,\n): UseValueDiffAlertDialogReturn {\n const [open, setOpen] = useState(false);\n const [nodeCount, setNodeCount] = useState(0);\n const [resolvePromise, setResolvePromise] =\n useState<(value: boolean) => void>();\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n const confirm = useCallback((count: number) => {\n setNodeCount(count);\n return new Promise<boolean>((resolve) => {\n setResolvePromise(() => resolve);\n setOpen(true);\n });\n }, []);\n\n const handleConfirm = () => {\n options?.onConfirm?.(nodeCount);\n resolvePromise?.(true);\n setOpen(false);\n };\n\n const handleCancel = () => {\n options?.onCancel?.(nodeCount);\n resolvePromise?.(false);\n setOpen(false);\n };\n\n const AlertDialog = (\n <MuiDialog\n open={open}\n onClose={handleCancel}\n maxWidth=\"md\"\n fullWidth\n aria-labelledby=\"value-diff-alert-dialog-title\"\n >\n <DialogTitle\n id=\"value-diff-alert-dialog-title\"\n sx={{ fontSize: \"1.125rem\", fontWeight: \"bold\" }}\n >\n Value Diff on {nodeCount} nodes\n </DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={handleCancel}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n <DialogContent>\n <Stack spacing=\"20px\">\n <Box>\n Value diff will be executed on {nodeCount} nodes in the Lineage,\n which can add extra costs to your bill.\n </Box>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ gap: 0.5 }}>\n <Button\n ref={cancelRef}\n onClick={handleCancel}\n variant=\"outlined\"\n color=\"neutral\"\n >\n Cancel\n </Button>\n <Button\n color=\"iochmara\"\n variant=\"contained\"\n onClick={handleConfirm}\n sx={{ ml: 1.5 }}\n >\n Execute\n </Button>\n </DialogActions>\n </MuiDialog>\n );\n\n return { confirm, AlertDialog };\n}\n","\"use client\";\n\nimport Button from \"@mui/material/Button\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport Typography from \"@mui/material/Typography\";\nimport type { ComponentType, ReactNode } from \"react\";\nimport type { RecceFeatureMode } from \"../../contexts/instance\";\nimport { formatDuration } from \"../../utils/formatTime\";\n\n/**\n * Props for the ServerDisconnectedModalContent component\n */\nexport interface ServerDisconnectedModalContentProps {\n /** Callback to attempt reconnection */\n connect: () => void;\n /** If provided, indicates the server was idle for this many seconds before timeout */\n idleSeconds?: number | null;\n}\n\n/**\n * Modal content displayed when the local server connection is lost.\n * Shows different messages for idle timeout vs unexpected disconnection.\n */\nexport function ServerDisconnectedModalContent({\n connect,\n idleSeconds,\n}: ServerDisconnectedModalContentProps) {\n const isIdleTimeout =\n idleSeconds !== undefined && idleSeconds !== null && idleSeconds > 0;\n\n return (\n <>\n <DialogTitle>Server Disconnected</DialogTitle>\n <DialogContent>\n {isIdleTimeout ? (\n <Typography>\n The server has been idle for {formatDuration(idleSeconds)} and was\n automatically stopped. Please restart the Recce server to continue.\n </Typography>\n ) : (\n <Typography>\n The server connection has been lost. Please restart the Recce server\n and try again.\n </Typography>\n )}\n </DialogContent>\n <DialogActions>\n <Button\n color=\"iochmara\"\n variant=\"contained\"\n onClick={() => {\n connect();\n }}\n >\n Retry\n </Button>\n </DialogActions>\n </>\n );\n}\n\n/**\n * Props for a link component that can wrap children\n */\nexport interface LinkComponentProps {\n href: string;\n children: ReactNode;\n}\n\n/**\n * Props for the RecceInstanceDisconnectedModalContent component\n */\nexport interface RecceInstanceDisconnectedModalContentProps {\n /** URL to restart the share instance */\n shareUrl: string;\n /** The feature mode (determines which message to display) */\n mode: Exclude<RecceFeatureMode, null>;\n /** URL for scheduling support calls (used in metadata-only mode) */\n supportCalendarUrl: string;\n /**\n * Optional link component for routing integration (e.g., Next.js Link).\n * If provided, used for \"read only\" mode navigation.\n * Should pass `href` to child and handle routing.\n */\n LinkComponent?: ComponentType<LinkComponentProps>;\n}\n\n/**\n * Modal content displayed when a cloud instance expires.\n * Shows different messages based on the feature mode:\n * - \"read only\": Share instance expired, offers restart\n * - \"metadata only\": Preview instance expired, offers contact option\n */\nexport function RecceInstanceDisconnectedModalContent({\n shareUrl,\n mode,\n supportCalendarUrl,\n LinkComponent,\n}: RecceInstanceDisconnectedModalContentProps) {\n const contents = {\n \"read only\": {\n title: \"Share Instance Expired\",\n body: \"This Share Instance has expired. Please restart the share instance.\",\n action: \"Restart\",\n link: shareUrl,\n },\n \"metadata only\": {\n title: \"Preview Instance Expired\",\n body: \"This Preview Instance has expired. To browse more, please book a meeting with us.\",\n action: \"Contact us\",\n link: supportCalendarUrl,\n },\n };\n\n const content = contents[mode];\n\n const button = (\n <Button color=\"iochmara\" variant=\"contained\">\n {content.action}\n </Button>\n );\n\n return (\n <>\n <DialogTitle>{content.title}</DialogTitle>\n <DialogContent>\n <Typography>{content.body}</Typography>\n </DialogContent>\n <DialogActions>\n {mode === \"read only\" ? (\n LinkComponent ? (\n <LinkComponent href={content.link}>{button}</LinkComponent>\n ) : (\n <a href={content.link}>{button}</a>\n )\n ) : (\n <Button\n color=\"iochmara\"\n variant=\"contained\"\n onClick={() => window.open(content.link, \"_blank\")}\n >\n {content.action}\n </Button>\n )}\n </DialogActions>\n </>\n );\n}\n","\"use client\";\n\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Typography from \"@mui/material/Typography\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport NextLink from \"next/link\";\nimport React, {\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { IoClose } from \"react-icons/io5\";\nimport {\n aggregateRuns,\n cacheKeys,\n getServerInfo,\n markRelaunchHintCompleted,\n} from \"../api\";\nimport {\n RecceInstanceDisconnectedModalContent,\n ServerDisconnectedModalContent,\n} from \"../components/lineage\";\nimport { toaster } from \"../components/ui/Toaster\";\nimport {\n buildLineageGraph,\n type EnvInfo,\n LineageGraphProvider,\n useIdleTimeout,\n useRecceInstanceContext,\n useRecceServerFlag,\n} from \"../contexts\";\nimport { trackSingleEnvironment } from \"../lib/api/track\";\nimport { PUBLIC_API_URL, RECCE_SUPPORT_CALENDAR_URL } from \"../lib/const\";\nimport { useApiConfig } from \"./useApiConfig\";\n\ntype LineageWatcherStatus = \"pending\" | \"connected\" | \"disconnected\";\ntype EnvWatcherStatus = undefined | \"relaunch\";\n\ninterface WebSocketRefreshEvent {\n eventType: \"created\" | \"updated\" | \"deleted\";\n srcPath: string;\n}\n\ninterface WebSocketBroadcastEvent {\n id: string;\n title?: string;\n description: string;\n status?: \"info\" | \"warning\" | \"success\" | \"error\";\n position?:\n | \"top\"\n | \"top-right\"\n | \"top-left\"\n | \"bottom\"\n | \"bottom-right\"\n | \"bottom-left\";\n duration?: number;\n}\n\ntype WebSocketPayload =\n | {\n command: \"refresh\";\n event: WebSocketRefreshEvent;\n }\n | {\n command: \"relaunch\";\n }\n | {\n command: \"broadcast\";\n event: WebSocketBroadcastEvent;\n };\n\ninterface UseLineageWatcherOptions {\n /**\n * Whether to enable WebSocket connection.\n * Set to false for cloud mode where WebSocket is not used.\n */\n enabled?: boolean;\n\n /**\n * Base URL for WebSocket connection.\n * If not provided, uses PUBLIC_API_URL.\n */\n baseUrl?: string;\n\n /**\n * API prefix to replace /api in WebSocket URL.\n * If not provided, uses default /api/ws path.\n */\n apiPrefix?: string;\n}\n\nfunction useLineageWatcher({\n enabled = true,\n baseUrl,\n apiPrefix,\n}: UseLineageWatcherOptions = {}) {\n const [artifactsUpdatedToastId, setArtifactsUpdatedToastId] = useState<\n string | undefined\n >(undefined);\n\n // use ref so that the callbacks can access the latest values\n const ref = useRef<{\n ws: WebSocket | undefined;\n status: LineageWatcherStatus;\n artifactsUpdatedToastId: string | undefined;\n }>({\n ws: undefined,\n status: \"pending\",\n artifactsUpdatedToastId: undefined,\n });\n\n // If disabled, always return \"connected\" status to avoid showing disconnect modal\n const [status, setStatus] = useState<LineageWatcherStatus>(\n enabled ? \"pending\" : \"connected\",\n );\n const [envStatus, setEnvStatus] = useState<EnvWatcherStatus>(undefined);\n\n // Update ref in useEffect to avoid updating during render\n useEffect(() => {\n ref.current.status = status;\n }, [status]);\n\n // Keep artifactsUpdatedToastId in sync with ref\n useEffect(() => {\n ref.current.artifactsUpdatedToastId = artifactsUpdatedToastId;\n }, [artifactsUpdatedToastId]);\n\n const queryClient = useQueryClient();\n\n const invalidateCaches = useCallback(() => {\n void queryClient.invalidateQueries({ queryKey: cacheKeys.lineage() });\n void queryClient.invalidateQueries({ queryKey: cacheKeys.checks() });\n void queryClient.invalidateQueries({ queryKey: cacheKeys.runs() });\n }, [queryClient]);\n\n const connect = useCallback(() => {\n function httpUrlToWebSocketUrl(url: string): string {\n return url.replace(/(http)(s)?:\\/\\//, \"ws$2://\");\n }\n\n // Use baseUrl if provided, otherwise fall back to PUBLIC_API_URL\n const effectiveBaseUrl = baseUrl ?? PUBLIC_API_URL;\n // Construct WebSocket path with apiPrefix if provided\n const wsPath = apiPrefix ? `${apiPrefix}/ws` : \"/api/ws\";\n const ws = new WebSocket(\n `${httpUrlToWebSocketUrl(effectiveBaseUrl)}${wsPath}`,\n );\n ref.current.ws = ws;\n\n ws.onopen = () => {\n ws.send(\"ping\"); // server will respond with 'pong'\n };\n\n // Handling websocket messages from the server\n ws.onmessage = (event) => {\n if (event.data === \"pong\") {\n if (ref.current.status === \"disconnected\") {\n invalidateCaches();\n }\n setStatus(\"connected\");\n return;\n }\n try {\n const data = JSON.parse(event.data as string) as WebSocketPayload;\n if (data.command === \"refresh\") {\n const { eventType, srcPath } = data.event;\n const [targetName, fileName] = srcPath.split(\"/\").slice(-2);\n // Extract filename without extension (browser-compatible alternative to path.parse)\n const name = fileName.replace(/\\.[^/.]+$/, \"\");\n const eventId = `${targetName}-${name}-${eventType}`;\n if (ref.current.artifactsUpdatedToastId == null) {\n setArtifactsUpdatedToastId(\n toaster.create({\n id: eventId,\n description: `Detected ${targetName} ${name} ${eventType}`,\n type: \"info\",\n duration: 5000,\n closable: true,\n }),\n );\n }\n invalidateCaches();\n } else if (data.command === \"relaunch\") {\n setEnvStatus(\"relaunch\");\n } else {\n // Handle broadcast events\n const { id, title, description, status, duration } = data.event;\n setArtifactsUpdatedToastId(\n toaster.create({\n id: id || \"broadcast\",\n title,\n description,\n type: status ?? \"info\",\n duration: duration ?? 5000,\n closable: true,\n }),\n );\n }\n } catch (err) {\n console.error(err);\n }\n };\n ws.onerror = (err) => {\n console.error(\"An error occurred during Handling WebSockets\", err);\n };\n ws.onclose = () => {\n setStatus((status) => {\n if (status === \"connected\") {\n return \"disconnected\";\n }\n return status;\n });\n\n ref.current.ws = undefined;\n };\n }, [invalidateCaches, baseUrl, apiPrefix]);\n\n useEffect(() => {\n // Skip WebSocket connection if disabled (e.g., cloud mode)\n if (!enabled) {\n return;\n }\n\n const refObj = ref.current;\n connect();\n return () => {\n if (refObj.ws) {\n refObj.ws.close();\n }\n };\n }, [connect, enabled]);\n\n return {\n connectionStatus: status,\n connect,\n envStatus: envStatus,\n };\n}\n\ninterface LineageGraphAdapterProps {\n children: React.ReactNode;\n}\n\n/**\n * LineageGraphAdapter - Bridges OSS data fetching with @datarecce/ui's LineageGraphProvider\n *\n * This adapter:\n * 1. Does data fetching (useQuery for lineage data)\n * 2. Handles WebSocket connection for real-time updates\n * 3. Renders disconnect/relaunch modals\n * 4. Wraps @datarecce/ui's LineageGraphProvider, passing fetched data as props\n *\n * The separation allows @datarecce/ui to be reusable (props-driven, no fetching)\n * while OSS app handles its own data fetching needs.\n */\nexport function LineageGraphAdapter({ children }: LineageGraphAdapterProps) {\n const {\n idleTimeout,\n remainingSeconds,\n isEnabled,\n setDisconnected,\n resetConnection,\n } = useIdleTimeout();\n\n // Get configured API client from context\n const { apiClient, apiPrefix, baseUrl } = useApiConfig();\n\n const queryServerInfo = useQuery({\n queryKey: cacheKeys.lineage(),\n queryFn: () => getServerInfo(apiClient),\n });\n\n const queryRunAggregated = useQuery({\n queryKey: cacheKeys.runsAggregated(),\n queryFn: () => aggregateRuns(apiClient),\n });\n\n const lineageGraph = useMemo(() => {\n const lineage = queryServerInfo.data?.lineage;\n if (!lineage?.base) {\n return undefined;\n }\n\n return buildLineageGraph(lineage.base, lineage.current, lineage.diff);\n }, [queryServerInfo.data]);\n\n const errorMessage = queryServerInfo.error?.message;\n const {\n state_metadata: stateMetadata,\n lineage,\n sqlmesh,\n demo: isDemoSite,\n codespace: isCodespace,\n review_mode: reviewMode,\n cloud_mode: cloudMode,\n file_mode: fileMode,\n filename: fileName,\n adapter_type: adapterType,\n git,\n pull_request: pullRequest,\n support_tasks: supportTasks,\n } = queryServerInfo.data ?? {\n demo: false,\n };\n\n const dbtBase = lineage?.base.manifest_metadata;\n const dbtCurrent = lineage?.current.manifest_metadata;\n\n const envInfo: EnvInfo = {\n stateMetadata,\n adapterType,\n git,\n pullRequest,\n dbt: {\n base: dbtBase,\n current: dbtCurrent,\n },\n sqlmesh,\n };\n\n // Pass apiPrefix and baseUrl to useLineageWatcher for WebSocket connection\n const { connectionStatus, connect, envStatus } = useLineageWatcher({\n enabled: true,\n baseUrl,\n apiPrefix,\n });\n\n // Handle connection status changes for idle timeout\n useEffect(() => {\n if (connectionStatus === \"disconnected\") {\n // Stop countdown and keep-alive when disconnected\n setDisconnected();\n } else if (connectionStatus === \"connected\") {\n // Reset countdown when reconnected (e.g., after Retry)\n resetConnection();\n }\n }, [connectionStatus, setDisconnected, resetConnection]);\n\n const { data: flags, isLoading } = useRecceServerFlag();\n const { featureToggles, shareUrl } = useRecceInstanceContext();\n const [relaunchHintOpen, setRelaunchHintOpen] = useState<boolean>(false);\n const [prevRelaunchCondition, setPrevRelaunchCondition] =\n useState<boolean>(false);\n const queryClient = useQueryClient();\n\n // Calculate if modal should be open (during render)\n const shouldShowRelaunch =\n !isLoading &&\n envStatus === \"relaunch\" &&\n flags?.single_env_onboarding === true &&\n flags.show_relaunch_hint;\n\n // Adjust state during render when condition changes\n if (shouldShowRelaunch !== prevRelaunchCondition) {\n setPrevRelaunchCondition(shouldShowRelaunch);\n setRelaunchHintOpen(shouldShowRelaunch);\n }\n\n // Track side effect only when modal opens (remains in effect)\n useEffect(() => {\n if (shouldShowRelaunch && relaunchHintOpen) {\n trackSingleEnvironment({ action: \"target_base_added\" });\n }\n }, [shouldShowRelaunch, relaunchHintOpen]);\n\n const handleRelaunchClose = () => {\n setRelaunchHintOpen(false);\n void markRelaunchHintCompleted(apiClient);\n void queryClient.invalidateQueries({ queryKey: cacheKeys.flag() });\n };\n\n // Callback handlers for the provider\n const handleRefetchLineageGraph = useCallback(() => {\n void queryRunAggregated.refetch();\n }, [queryRunAggregated]);\n\n const handleRefetchRunsAggregated = useCallback(() => {\n void queryRunAggregated.refetch();\n }, [queryRunAggregated]);\n\n return (\n <>\n <LineageGraphProvider\n lineageGraph={lineageGraph}\n envInfo={envInfo}\n reviewMode={reviewMode}\n cloudMode={cloudMode}\n fileMode={fileMode}\n fileName={fileName}\n isDemoSite={isDemoSite ?? false}\n isCodespace={isCodespace}\n isLoading={queryServerInfo.isLoading}\n error={errorMessage}\n supportTasks={supportTasks}\n onRefetchLineageGraph={handleRefetchLineageGraph}\n runsAggregated={queryRunAggregated.data}\n onRefetchRunsAggregated={handleRefetchRunsAggregated}\n >\n {children}\n </LineageGraphProvider>\n\n <MuiDialog\n open={connectionStatus === \"disconnected\"}\n onClose={() => void 0}\n >\n {shareUrl && featureToggles.mode !== null ? (\n <RecceInstanceDisconnectedModalContent\n shareUrl={shareUrl}\n mode={featureToggles.mode}\n supportCalendarUrl={RECCE_SUPPORT_CALENDAR_URL}\n LinkComponent={({\n href,\n children,\n }: {\n href: string;\n children: ReactNode;\n }) => {\n return (\n <NextLink href={href} passHref>\n {children}\n </NextLink>\n );\n }}\n />\n ) : (\n <ServerDisconnectedModalContent\n connect={connect}\n idleSeconds={\n // Only show idle time if disconnected due to idle timeout\n // (idle timeout enabled AND remaining time was near zero)\n isEnabled &&\n idleTimeout !== null &&\n remainingSeconds !== null &&\n remainingSeconds <= 5\n ? idleTimeout - Math.max(0, remainingSeconds)\n : undefined\n }\n />\n )}\n </MuiDialog>\n\n {flags?.single_env_onboarding && (\n <MuiDialog open={relaunchHintOpen} onClose={handleRelaunchClose}>\n <DialogTitle>Target-base Added</DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={handleRelaunchClose}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n <DialogContent>\n <Typography>Please restart the Recce server.</Typography>\n </DialogContent>\n <DialogActions>\n <Button\n color=\"iochmara\"\n variant=\"contained\"\n onClick={handleRelaunchClose}\n >\n Got it!\n </Button>\n </DialogActions>\n </MuiDialog>\n )}\n </>\n );\n}\n\n// Note: useLineageGraphContext and useRunsAggregated are now imported directly from @datarecce/ui/contexts\n// This adapter only exports LineageGraphAdapter component and OSS-specific types (EnvInfo)\n","\"use client\";\n\nimport { type ReactNode, useState } from \"react\";\nimport { QueryProvider, useQueryContext } from \"../providers\";\n\ninterface QueryContextAdapterProps {\n children: ReactNode;\n}\n\nexport const defaultSqlQuery = 'select * from {{ ref(\"mymodel\") }}';\n\n/**\n * OSS-compatible QueryContext type with required fields.\n * The @datarecce/ui QueryContextType has optional OSS fields,\n * but OSS components expect them to be defined.\n */\nexport interface OSSQueryContext {\n sqlQuery: string;\n setSqlQuery: (sql: string) => void;\n primaryKeys: string[] | undefined;\n setPrimaryKeys: (pks: string[] | undefined) => void;\n isCustomQueries: boolean;\n setCustomQueries: (isCustom: boolean) => void;\n baseSqlQuery: string;\n setBaseSqlQuery: (sql: string) => void;\n}\n\n/**\n * QueryContextAdapter bridges OSS with @datarecce/ui's QueryProvider.\n *\n * Unlike CheckContextAdapter, this adapter manages internal state because\n * OSS's RecceQueryContext manages input state (sqlQuery, primaryKeys, etc.)\n * via useState, while @datarecce/ui's QueryProvider is props-driven.\n *\n * This adapter:\n * 1. Creates internal state matching OSS's interface\n * 2. Passes state and setters to QueryProvider as props\n * 3. Allows consumers to use the same hooks as before\n */\nexport function QueryContextAdapter({ children }: QueryContextAdapterProps) {\n // OSS input state\n const [sqlQuery, setSqlQuery] = useState<string>(defaultSqlQuery);\n const [baseSqlQuery, setBaseSqlQuery] = useState<string>(defaultSqlQuery);\n const [isCustomQueries, setCustomQueries] = useState<boolean>(false);\n const [primaryKeys, setPrimaryKeys] = useState<string[] | undefined>();\n\n return (\n <QueryProvider\n // Pass state as props to @datarecce/ui QueryProvider\n sql={sqlQuery}\n sqlQuery={sqlQuery}\n setSqlQuery={setSqlQuery}\n primaryKeys={primaryKeys}\n setPrimaryKeys={setPrimaryKeys}\n isCustomQueries={isCustomQueries}\n setCustomQueries={setCustomQueries}\n baseSqlQuery={baseSqlQuery}\n setBaseSqlQuery={setBaseSqlQuery}\n >\n {children}\n </QueryProvider>\n );\n}\n\n// Note: QueryContextType, QueryProviderProps, QueryResult are now imported directly from @datarecce/ui/providers\n// This adapter only exports QueryContextAdapter, useRecceQueryContext, defaultSqlQuery, and OSSQueryContext type\n\n// No-op fallbacks for when hook is used outside provider\nconst noopSetSqlQuery = (_sql: string) => {\n // Intentionally empty - fallback when used outside QueryContextAdapter\n};\nconst noopSetPrimaryKeys = (_pks: string[] | undefined) => {\n // Intentionally empty - fallback when used outside QueryContextAdapter\n};\nconst noopSetCustomQueries = (_isCustom: boolean) => {\n // Intentionally empty - fallback when used outside QueryContextAdapter\n};\nconst noopSetBaseSqlQuery = (_sql: string) => {\n // Intentionally empty - fallback when used outside QueryContextAdapter\n};\n\n/**\n * OSS-compatible hook that returns the query context with guaranteed non-optional fields.\n * This wraps @datarecce/ui's useQueryContext and provides type safety for OSS components.\n */\nexport function useRecceQueryContext(): OSSQueryContext {\n const ctx = useQueryContext();\n\n // Return OSS-compatible interface with guaranteed values\n // The QueryContextAdapter ensures these are always set\n return {\n sqlQuery: ctx.sqlQuery ?? defaultSqlQuery,\n setSqlQuery: ctx.setSqlQuery ?? noopSetSqlQuery,\n primaryKeys: ctx.primaryKeys,\n setPrimaryKeys: ctx.setPrimaryKeys ?? noopSetPrimaryKeys,\n isCustomQueries: ctx.isCustomQueries ?? false,\n setCustomQueries: ctx.setCustomQueries ?? noopSetCustomQueries,\n baseSqlQuery: ctx.baseSqlQuery ?? defaultSqlQuery,\n setBaseSqlQuery: ctx.setBaseSqlQuery ?? noopSetBaseSqlQuery,\n };\n}\n\n// Note: useQueryContext is now imported directly from @datarecce/ui/providers\n","\"use client\";\n\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { usePathname, useRouter } from \"next/navigation\";\nimport React, {\n type ComponentType,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport type { Run, RunParamTypes, RunType } from \"../api\";\nimport {\n cacheKeys,\n type SubmitRunTrackProps,\n searchRuns,\n submitRun,\n} from \"../api\";\nimport {\n findByRunType,\n type RegistryEntry,\n type RunFormParamTypes,\n type RunFormProps,\n RunModalOss,\n} from \"../components/run\";\nimport { toaster } from \"../components/ui/Toaster\";\nimport {\n type AxiosQueryParams,\n RecceActionProvider,\n type RecceActionOptions as UIRecceActionOptions,\n useRouteConfig,\n useRecceActionContext as useUIRecceActionContext,\n} from \"../contexts\";\nimport { useApiConfig } from \"./useApiConfig\";\n\n// Note: AxiosQueryParams is now imported directly from @datarecce/ui/contexts\n// This adapter only exports RecceActionAdapter component and RecceActionOptions type\n\n/**\n * Extended options for OSS that include registry-specific features\n */\nexport interface RecceActionOptions {\n showForm: boolean;\n showLast?: boolean;\n trackProps?: SubmitRunTrackProps;\n}\n\n/**\n * Internal state for managing the RunModal\n */\ninterface RunActionInternal {\n session: string;\n title: string;\n type: RunType;\n params?: RunFormParamTypes;\n lastRun?: Run;\n options?: RecceActionOptions;\n RunForm?: ComponentType<RunFormProps<RunFormParamTypes>>;\n}\n\n/**\n * Custom hook to close modal on location changes\n */\nfunction useCloseModalEffect(onClose: () => void) {\n const pathname = usePathname();\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: Specifically run on location changes\n useEffect(() => {\n onClose();\n }, [onClose, pathname]);\n}\n\ninterface RecceActionAdapterProps {\n children: React.ReactNode;\n}\n\n/**\n * RecceActionAdapter - Bridges OSS action handling with @datarecce/ui's RecceActionProvider\n *\n * This adapter:\n * 1. Keeps submitRun API logic (OSS-specific)\n * 2. Keeps RunModal UI rendering (OSS-specific)\n * 3. Keeps cache invalidation via useQueryClient (OSS-specific)\n * 4. Uses findByRunType registry lookup (OSS-specific)\n * 5. Wraps @datarecce/ui's RecceActionProvider, passing callbacks as props\n *\n * The separation allows @datarecce/ui to be reusable (props-driven, no API calls)\n * while OSS app handles its own API calls, modals, and cache management.\n */\nexport function RecceActionAdapter({ children }: RecceActionAdapterProps) {\n const { apiClient } = useApiConfig();\n const [action, setAction] = useState<RunActionInternal>();\n\n // Modal state\n const [isModalOpen, setModalOpen] = useState(false);\n const onModalOpen = useCallback(() => setModalOpen(true), []);\n const onModalClose = useCallback(() => setModalOpen(false), []);\n\n const router = useRouter();\n const pathname = usePathname();\n const queryClient = useQueryClient();\n const { basePath } = useRouteConfig();\n\n // Store a ref to the showRunId function from the provider\n // This is set by the inner component after the provider mounts\n const showRunIdRef = useRef<\n ((runId: string, refreshHistory?: boolean) => void) | null\n >(null);\n\n // Close modal on location changes\n useCloseModalEffect(onModalClose);\n\n /**\n * Callback for when a run result should be shown.\n * Handles cache invalidation when refreshHistory is true.\n */\n const handleShowRunId = useCallback(\n async (runId: string, refreshHistory?: boolean) => {\n if (refreshHistory !== false) {\n await queryClient.invalidateQueries({ queryKey: cacheKeys.runs() });\n }\n },\n [queryClient],\n );\n\n /**\n * Callback for executing a run action.\n * This is the core OSS logic that the @datarecce/ui provider delegates to.\n *\n * It:\n * - Looks up the run type in the registry\n * - Either submits the run directly or shows a form modal\n * - Handles errors with toast notifications\n * - Returns the run ID for the provider to track\n */\n const handleRunAction = useCallback(\n async (\n type: string,\n params?: AxiosQueryParams,\n options?: UIRecceActionOptions,\n ): Promise<string | undefined> => {\n try {\n const session = new Date().getTime().toString();\n let lastRun: Run | undefined = undefined;\n\n // Cast to full OSS options type (includes showLast)\n const ossOptions = options as RecceActionOptions | undefined;\n\n if (ossOptions?.showLast) {\n const runs = await searchRuns(type, params, 1, apiClient);\n if (runs.length === 1) {\n lastRun = runs[0] as Run;\n }\n }\n\n const run = findByRunType(type as RunType);\n const RunResultView = run.RunResultView as\n | RegistryEntry[\"RunResultView\"]\n | undefined;\n const { title, RunForm } = run;\n\n if (RunResultView === undefined) {\n throw new Error(`Run type ${type} does not have a result view`);\n }\n\n if (RunForm === undefined || !options?.showForm) {\n // Direct submission - no form needed\n const { run_id } = await submitRun(\n type as RunType,\n params,\n {\n nowait: true,\n trackProps: ossOptions?.trackProps,\n },\n apiClient,\n );\n\n await queryClient.invalidateQueries({ queryKey: cacheKeys.runs() });\n\n // Call showRunId BEFORE navigation (matching original RecceActionContext behavior)\n // This ensures state is set before any navigation-triggered re-renders\n if (showRunIdRef.current) {\n showRunIdRef.current(run_id);\n }\n\n // Navigate to lineage base only if we're on a lineage subpath\n // (e.g., /lineage/node/test → /lineage). Skip when already on base\n // to avoid unnecessary re-renders that reset zoom and node focus.\n const lineagePath = `${basePath}/lineage`;\n if (\n pathname.startsWith(`${lineagePath}/`) &&\n pathname !== lineagePath\n ) {\n router.push(lineagePath);\n }\n\n // Return undefined since we already called showRunId via ref\n return undefined;\n }\n // Show form modal\n setAction({\n session,\n title,\n type: type as RunType,\n params,\n lastRun,\n options: ossOptions,\n RunForm,\n });\n onModalOpen();\n\n // Return undefined - the modal will handle submission\n return undefined;\n } catch (e: unknown) {\n toaster.create({\n title: \"Failed to submit a run\",\n description: e instanceof Error ? e.message : undefined,\n type: \"error\",\n duration: 5000,\n closable: true,\n });\n return undefined;\n }\n },\n [onModalOpen, queryClient, apiClient, pathname, router, basePath],\n );\n\n /**\n * Handle form execution from RunModal.\n * Submits the run and triggers showRunId via the ref.\n */\n const handleModalExecute = useCallback(\n async (type: RunType, params: RunParamTypes) => {\n try {\n onModalClose();\n const { run_id } = await submitRun(\n type,\n params,\n {\n nowait: true,\n trackProps: action?.options?.trackProps,\n },\n apiClient,\n );\n\n // Use the ref to call showRunId from the provider context\n if (showRunIdRef.current) {\n showRunIdRef.current(run_id);\n }\n } catch (e: unknown) {\n toaster.create({\n title: \"Failed to submit a run\",\n description: e instanceof Error ? e.message : undefined,\n type: \"error\",\n duration: 5000,\n closable: true,\n });\n }\n },\n [onModalClose, action?.options?.trackProps, apiClient],\n );\n\n return (\n <RecceActionProvider\n onRunAction={handleRunAction}\n onShowRunId={handleShowRunId}\n >\n {/* Inner component captures showRunId ref */}\n <RecceActionAdapterInner showRunIdRef={showRunIdRef}>\n {children}\n </RecceActionAdapterInner>\n\n {/* RunModal for form-based runs */}\n {action && (\n <RunModalOss\n key={action.session}\n isOpen={isModalOpen}\n onClose={onModalClose}\n onExecute={handleModalExecute}\n title={action.title}\n type={action.type}\n params={action.params}\n initialRun={action.lastRun}\n RunForm={\n action.options?.showForm && action.RunForm\n ? action.RunForm\n : undefined\n }\n />\n )}\n </RecceActionProvider>\n );\n}\n\n/**\n * Inner component that captures the showRunId function from context\n * and stores it in the parent's ref for use by modal execution.\n */\ninterface RecceActionAdapterInnerProps {\n children: React.ReactNode;\n showRunIdRef: React.MutableRefObject<\n ((runId: string, refreshHistory?: boolean) => void) | null\n >;\n}\n\nfunction RecceActionAdapterInner({\n children,\n showRunIdRef,\n}: RecceActionAdapterInnerProps) {\n const { showRunId } = useUIRecceActionContext();\n\n // Store the showRunId function in the ref for parent to use\n useEffect(() => {\n showRunIdRef.current = showRunId;\n return () => {\n showRunIdRef.current = null;\n };\n }, [showRunId, showRunIdRef]);\n\n return <>{children}</>;\n}\n\n// Note: useRecceActionContext is now imported directly from @datarecce/ui/contexts\n// This adapter only exports RecceActionAdapter component and OSS-specific types\n","import React, { createContext, useContext, useState } from \"react\";\nimport { shareState } from \"../api\";\nimport { useApiConfig } from \"./useApiConfig\";\n\ninterface ShareStateProps {\n shareUrl?: string;\n isLoading: boolean;\n error?: string;\n handleShareClick: () => Promise<void>;\n}\n\nconst ShareState = createContext<ShareStateProps | undefined>(undefined);\n\nexport function RecceShareStateContextProvider({\n children,\n}: {\n children: React.ReactNode;\n}) {\n const [shareUrl, setShareUrl] = useState<string>();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string>();\n const { apiClient } = useApiConfig();\n\n const handleShareClick = async () => {\n setIsLoading(true);\n setError(undefined);\n setShareUrl(undefined);\n try {\n const response = await shareState(apiClient);\n if (response.status !== \"success\") {\n setError(response.message);\n return;\n }\n setShareUrl(response.share_url);\n } catch (err) {\n setError((err as Error).message);\n } finally {\n setIsLoading(false);\n }\n };\n\n return (\n <ShareState.Provider\n value={{ shareUrl, isLoading, error, handleShareClick }}\n >\n {children}\n </ShareState.Provider>\n );\n}\n\nexport const useRecceShareStateContext = () => {\n const context = useContext(ShareState);\n if (!context) {\n throw new Error(\n \"useRecceShareStateContext must be used within a RecceShareStateContextProvider\",\n );\n }\n return context;\n};\n","import React from \"react\";\nimport { RecceInstanceInfoProvider } from \"../contexts\";\nimport { CheckContextAdapter } from \"./CheckContextAdapter\";\nimport { LineageGraphAdapter } from \"./LineageGraphAdapter\";\nimport { QueryContextAdapter } from \"./QueryContextAdapter\";\nimport { RecceActionAdapter } from \"./RecceActionAdapter\";\nimport { RecceShareStateContextProvider } from \"./RecceShareStateContext\";\n\ninterface RecceContextProps {\n children: React.ReactNode;\n}\n\n/**\n * Main context provider for Recce application.\n *\n * For custom API configuration (e.g., recce-cloud), wrap this provider\n * with ApiConfigProvider:\n *\n * ```tsx\n * <ApiConfigProvider\n * apiPrefix=\"/api/v2/sessions/abc123\"\n * authToken=\"eyJ...\"\n * >\n * <RecceContextProvider>\n * {children}\n * </RecceContextProvider>\n * </ApiConfigProvider>\n * ```\n *\n * When used without ApiConfigProvider (OSS mode), hooks will use\n * the default axios client with standard /api/* endpoints.\n */\nexport default function RecceContextProvider({ children }: RecceContextProps) {\n return (\n <RecceInstanceInfoProvider>\n <RecceShareStateContextProvider>\n <QueryContextAdapter>\n <LineageGraphAdapter>\n <RecceActionAdapter>\n <CheckContextAdapter>{children}</CheckContextAdapter>\n </RecceActionAdapter>\n </LineageGraphAdapter>\n </QueryContextAdapter>\n </RecceShareStateContextProvider>\n </RecceInstanceInfoProvider>\n );\n}\n","import Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport { format } from \"date-fns\";\nimport saveAs from \"file-saver\";\nimport { toCanvas } from \"html-to-image\";\nimport React, {\n type RefObject,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { IoClose } from \"react-icons/io5\";\nimport { PiCopy, PiInfo } from \"react-icons/pi\";\nimport type { DataGridHandle } from \"../primitives\";\nimport { colors } from \"../theme\";\nimport { useClipBoardToast } from \"./useClipBoardToast\";\n\n// Dynamic import for html2canvas-pro (externalized to consuming app)\ntype Html2CanvasFn = (\n element: HTMLElement,\n options?: Record<string, unknown>,\n) => Promise<HTMLCanvasElement>;\n\nconst loadHtml2Canvas = async (): Promise<Html2CanvasFn> => {\n // Use the package's main export - it resolves to ESM via package.json exports\n const module = await import(\"html2canvas-pro\");\n return module.default as Html2CanvasFn;\n};\n\n// Type to represent DataGridHandle which may have an element property\ntype DataGridRefType = DataGridHandle & { element?: HTMLElement };\n\n// Helper function to safely extract HTMLElement from DataGridHandle\nconst getHTMLElementFromRef = (\n refCurrent: DataGridRefType,\n): HTMLElement | undefined => {\n // DataGridHandle might have an 'element' property containing the actual HTMLElement\n if (\"element\" in refCurrent) {\n return refCurrent.element;\n }\n // Otherwise, treat the ref itself as the HTMLElement\n return refCurrent as unknown as HTMLElement;\n};\n\nexport const IGNORE_SCREENSHOT_CLASS = \"ignore-screenshot\";\n\nexport const highlightBoxShadow =\n \"rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px\";\n\nexport interface HookOptions {\n renderLibrary?: \"html2canvas\" | \"html-to-image\";\n imageType?: \"png\" | \"jpeg\";\n backgroundColor?: string | null;\n boardEffect?: boolean;\n shadowEffect?: boolean;\n borderStyle?: string;\n borderRadius?: string;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n onClipboardNotDefined?: (blob: Blob) => void;\n ignoreElements?: (element: Element) => boolean;\n}\n\nexport interface BlobHookReturn {\n status: \"idle\" | \"loading\" | \"success\" | \"error\";\n isLoading: boolean;\n isErrored: boolean;\n isSuccess: boolean;\n toImage: () => void;\n ref: RefObject<HTMLElement | null>;\n}\n\nexport function useCopyToClipboard({\n renderLibrary = \"html2canvas\",\n imageType = \"png\",\n backgroundColor = null,\n boardEffect = true,\n shadowEffect = false,\n borderStyle = `solid 1px ${colors.neutral[300]}`,\n borderRadius = \"10px\",\n onSuccess,\n onError,\n ignoreElements,\n}: HookOptions) {\n const [status, setStatus] = useState<\n \"idle\" | \"loading\" | \"success\" | \"error\"\n >(\"idle\");\n const ref = useRef<DataGridRefType>(null);\n\n // ImageDownloadModal is used for browsers that don't support ClipboardItem\n const { onOpen, setImgBlob, ImageDownloadModal } = useImageDownloadModal();\n\n const toImage = async () => {\n if (!ref.current) {\n console.error(\"No node to use for screenshot\");\n throw new Error(\"No node to use for screenshot\");\n }\n\n const nodeToUse = getHTMLElementFromRef(ref.current);\n if (!nodeToUse) {\n console.error(\"Could not get HTMLElement from ref\");\n throw new Error(\"Could not get HTMLElement from ref\");\n }\n const overflow = nodeToUse.style.overflow;\n const border = nodeToUse.style.border;\n const radius = nodeToUse.style.borderRadius;\n const background = nodeToUse.style.backgroundColor;\n const heigh = nodeToUse.style.height;\n\n function resetStyles() {\n // nodeToUse is verified non-null before resetStyles is defined\n // Capture in local const to satisfy linter\n const node = nodeToUse;\n if (node) {\n node.style.overflow = overflow;\n node.style.border = border;\n node.style.borderRadius = radius;\n node.style.backgroundColor = background;\n node.style.height = heigh;\n }\n }\n\n try {\n nodeToUse.style.overflow = \"hidden\";\n nodeToUse.style.border = boardEffect ? borderStyle : \"\";\n nodeToUse.style.borderRadius = boardEffect ? borderRadius : \"\";\n nodeToUse.style.backgroundColor = backgroundColor ?? colors.neutral[100];\n // after firefox v125, html2canvas can't get the correct style height of the element to clone\n nodeToUse.style.height = `${String(nodeToUse.offsetHeight)}px`;\n\n // Add style to make images inline-block\n // ref: https://github.com/niklasvh/html2canvas/issues/2107#issuecomment-1316354455\n const style = document.createElement(\"style\");\n document.head.appendChild(style);\n style.sheet?.insertRule(\n \"body > div:last-child img { display: inline-block; }\",\n );\n const filter = ignoreElements\n ? (n: HTMLElement) => !ignoreElements(n)\n : undefined;\n\n setStatus(\"loading\");\n let canvas: HTMLCanvasElement;\n if (renderLibrary === \"html2canvas\") {\n const html2canvas = await loadHtml2Canvas();\n canvas = await html2canvas(nodeToUse, {\n logging: false,\n backgroundColor: backgroundColor ?? colors.neutral[100],\n ignoreElements: ignoreElements,\n });\n } else {\n canvas = await toCanvas(nodeToUse, {\n filter: filter,\n }); // Use html-to-image for copy reactflow graph\n }\n\n style.remove();\n const outputCanvas = shadowEffect\n ? document.createElement(\"canvas\")\n : canvas;\n\n if (shadowEffect) {\n // Add shadow effect\n outputCanvas.width = canvas.width + 80;\n outputCanvas.height = canvas.height + 80;\n const ctx = outputCanvas.getContext(\"2d\");\n if (ctx) {\n ctx.shadowColor = \"rgba(0, 0, 0, 0.5)\";\n ctx.shadowBlur = 20;\n ctx.shadowOffsetX = 10;\n ctx.shadowOffsetY = 10;\n ctx.drawImage(canvas, 40, 40);\n } else {\n console.error(\"Error getting canvas context\");\n throw new Error(\"Error getting canvas context to add shadow effect\");\n }\n }\n\n const response = await fetch(outputCanvas.toDataURL());\n return await response.blob();\n } catch (error: unknown) {\n console.error(\"Error converting to image\", error);\n throw error;\n } finally {\n resetStyles();\n }\n };\n\n const copyToClipboard = async () => {\n try {\n await navigator.clipboard.write([\n new ClipboardItem({ [`image/${imageType}`]: toImage() }),\n ]);\n setStatus(\"success\");\n if (onSuccess) {\n onSuccess();\n }\n } catch (error) {\n if ((error as Error).message === \"ClipboardItem is not defined\") {\n const blob = await toImage();\n setImgBlob(blob);\n onOpen();\n setStatus(\"success\");\n } else {\n setStatus(\"error\");\n console.error(\"Error copying to clipboard\", error);\n if (onError) {\n onError(error);\n }\n }\n }\n };\n\n return {\n status,\n isLoading: status === \"loading\",\n isErrored: status === \"error\",\n isSuccess: status === \"success\",\n copyToClipboard,\n ImageDownloadModal,\n ref,\n };\n}\n\nexport function useCopyToClipboardButton(options?: HookOptions) {\n const { successToast, failToast } = useClipBoardToast();\n\n const { isLoading, copyToClipboard, ImageDownloadModal, ref } =\n useCopyToClipboard({\n imageType: \"png\",\n shadowEffect: true,\n backgroundColor: options?.backgroundColor ?? colors.neutral[100],\n onSuccess: () => {\n successToast(\"Copied the query result as an image to clipboard\");\n },\n onError: (error) => {\n console.error(\"Error taking screenshot\", error);\n failToast(\"Failed to copy image to clipboard\", error);\n },\n });\n\n const onMouseEnter = useCallback(() => {\n if (ref.current) {\n const nodeToUse = getHTMLElementFromRef(ref.current);\n if (nodeToUse) {\n nodeToUse.style.boxShadow = highlightBoxShadow;\n nodeToUse.style.transition = \"box-shadow 0.5s ease-in-out\";\n }\n }\n }, [ref]);\n\n const onMouseLeave = useCallback(() => {\n if (ref.current) {\n const nodeToUse = getHTMLElementFromRef(ref.current);\n if (nodeToUse) {\n nodeToUse.style.boxShadow = \"\";\n }\n }\n }, [ref]);\n\n const onCopyToClipboard = useCallback(async () => {\n if (ref.current) {\n await copyToClipboard();\n const nodeToUse = getHTMLElementFromRef(ref.current);\n if (nodeToUse) {\n nodeToUse.style.boxShadow = \"\";\n }\n } else {\n failToast(\"Failed to copy image to clipboard\", \"No content to copy\");\n }\n }, [ref, copyToClipboard, failToast]);\n\n function CopyToClipboardButton({\n imageType = \"png\",\n ...props\n }: {\n imageType?: \"png\" | \"jpeg\";\n }) {\n return (\n <>\n <Button\n size=\"small\"\n sx={{ position: \"absolute\", bottom: 16, right: 16 }}\n disabled={isLoading}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n onClick={onCopyToClipboard}\n startIcon={<PiCopy />}\n {...props}\n >\n Copy to Clipboard\n </Button>\n <ImageDownloadModal />\n </>\n );\n }\n\n return {\n ref,\n CopyToClipboardButton,\n onMouseEnter,\n onMouseLeave,\n onCopyToClipboard,\n };\n}\n\nexport function useImageDownloadModal() {\n const [open, setOpen] = useState(false);\n const [imgBlob, setImgBlob] = useState<Blob>();\n\n const onOpen = () => setOpen(true);\n const onClose = () => setOpen(false);\n\n function ImageDownloadModal() {\n const [base64Img, setBase64Img] = useState<string>();\n\n useEffect(() => {\n if (!imgBlob) {\n return;\n }\n const reader = new FileReader();\n reader.readAsDataURL(imgBlob);\n reader.onloadend = (e) => {\n if (e.target?.result != null) {\n setBase64Img(e.target.result as string);\n }\n };\n }, []);\n\n const onDownload = () => {\n if (!imgBlob) {\n return;\n }\n const now = new Date();\n const fileName = `recce-screenshot-${format(now, \"yyyy-MM-dd-HH-mm-ss\")}.png`;\n saveAs(imgBlob, fileName);\n onClose();\n };\n\n return (\n <MuiDialog open={open} onClose={onClose} maxWidth=\"sm\" fullWidth>\n <DialogTitle>Screenshot Preview</DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={onClose}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n <DialogContent>\n <Stack sx={{ px: \"10px\", gap: \"10px\" }}>\n <Stack direction=\"row\" alignItems=\"center\" spacing=\"5px\">\n <Box component={PiInfo} sx={{ color: \"error.main\" }} />\n <Typography sx={{ fontWeight: 500, display: \"inline\" }}>\n Copy to the Clipboard\n </Typography>{\" \"}\n is not supported in the current browser\n </Stack>\n <Typography>Please download it directly</Typography>\n </Stack>\n <Box\n component=\"img\"\n src={base64Img}\n alt=\"screenshot\"\n sx={{ maxWidth: \"100%\" }}\n />\n </DialogContent>\n\n <DialogActions>\n <Button sx={{ mr: 1.5 }} onClick={onClose}>\n Close\n </Button>\n <Button color=\"iochmara\" variant=\"contained\" onClick={onDownload}>\n Download\n </Button>\n </DialogActions>\n </MuiDialog>\n );\n }\n\n return {\n onOpen,\n setImgBlob,\n ImageDownloadModal,\n };\n}\n","/**\n * Custom hook for managing check events (timeline/conversation).\n *\n * Provides data fetching with polling for real-time updates,\n * and mutation functions for CRUD operations.\n */\n\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n cacheKeys,\n createComment,\n deleteComment,\n listCheckEvents,\n updateComment,\n} from \"../api\";\nimport { useApiConfig } from \"./useApiConfig\";\n\nconst POLLING_INTERVAL = 10000; // 10 seconds\n\ninterface UseCheckEventsOptions {\n enabled?: boolean;\n}\n\nexport function useCheckEvents(\n checkId: string,\n options: UseCheckEventsOptions = {},\n) {\n const { enabled = true } = options;\n const queryClient = useQueryClient();\n const { apiClient } = useApiConfig();\n\n // Fetch events with polling\n const {\n data: events,\n isLoading,\n error,\n refetch,\n } = useQuery({\n queryKey: cacheKeys.checkEvents(checkId),\n queryFn: () => listCheckEvents(checkId, apiClient),\n enabled,\n refetchInterval: POLLING_INTERVAL,\n refetchIntervalInBackground: false,\n });\n\n // Create comment mutation\n const createCommentMutation = useMutation({\n mutationFn: (content: string) => createComment(checkId, content, apiClient),\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: cacheKeys.checkEvents(checkId),\n });\n },\n });\n\n // Update comment mutation\n const updateCommentMutation = useMutation({\n mutationFn: ({ eventId, content }: { eventId: string; content: string }) =>\n updateComment(checkId, eventId, content, apiClient),\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: cacheKeys.checkEvents(checkId),\n });\n },\n });\n\n // Delete comment mutation\n const deleteCommentMutation = useMutation({\n mutationFn: (eventId: string) => deleteComment(checkId, eventId, apiClient),\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: cacheKeys.checkEvents(checkId),\n });\n },\n });\n\n return {\n events: events ?? [],\n isLoading,\n error,\n refetch,\n\n // Mutations\n createComment: createCommentMutation.mutate,\n isCreatingComment: createCommentMutation.isPending,\n createCommentError: createCommentMutation.error,\n\n // Use mutateAsync for updateComment to allow awaiting in UI\n updateComment: updateCommentMutation.mutateAsync,\n isUpdatingComment: updateCommentMutation.isPending,\n updateCommentError: updateCommentMutation.error,\n\n // Use mutateAsync for deleteComment to allow awaiting in UI\n deleteComment: deleteCommentMutation.mutateAsync,\n isDeletingComment: deleteCommentMutation.isPending,\n deleteCommentError: deleteCommentMutation.error,\n };\n}\n","/**\n * @recce-migration NOT_APPLICABLE\n *\n * This hook is specific to Recce OSS and should not be migrated to @datarecce/ui.\n *\n * Reason: Server lifetime countdown is an OSS-specific feature for local\n * development servers. It shows remaining time before auto-shutdown.\n * Cloud deployments do not have this concept.\n *\n * If this changes in the future, consider:\n * - This is unlikely to be needed in cloud/shared contexts\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useTimeout } from \"usehooks-ts\";\nimport { toaster } from \"../components/ui/Toaster\";\n\nconst COUNTDOWN_CONFIG = {\n TOAST_ID: \"lifetime-countdown\",\n WARNING_THRESHOLD: 60, // seconds before expiry to show warning\n UPDATE_INTERVAL: 1000, // milliseconds\n MESSAGE: (seconds: number) =>\n `The server will be closed in ${seconds} seconds.`,\n STYLE: {\n fontFamily: \"monospace\",\n },\n} as const;\n\n/**\n * Hook to manage countdown toast notifications for server lifetime\n * @param lifetimeExpiredAt - Date when the server will expire\n */\nexport function useCountdownToast(lifetimeExpiredAt: Date | undefined) {\n const countdownToast = toaster;\n const [countdownToastId, setCountdownToastId] = useState<string | null>(null);\n const countdownIntervalRef = useRef<NodeJS.Timeout>(undefined);\n\n const calculateRemainingSeconds = useCallback(() => {\n if (!lifetimeExpiredAt) return 0;\n\n const now = new Date();\n const remaining = Math.floor(\n (lifetimeExpiredAt.getTime() - now.getTime()) / 1000,\n );\n return Math.max(0, remaining); // Ensure we don't return negative values\n }, [lifetimeExpiredAt]);\n\n const cleanupToast = useCallback(() => {\n if (countdownToastId != null) {\n countdownToast.remove(countdownToastId);\n setCountdownToastId(null);\n }\n }, [countdownToastId]);\n\n const updateToast = useCallback(() => {\n if (countdownToastId == null) return;\n\n const remainingSeconds = calculateRemainingSeconds();\n if (remainingSeconds <= 0) {\n cleanupToast();\n return;\n }\n\n countdownToast.update(countdownToastId, {\n description: COUNTDOWN_CONFIG.MESSAGE(remainingSeconds),\n });\n }, [countdownToastId, calculateRemainingSeconds, cleanupToast]);\n\n const showToast = useCallback(() => {\n if (!lifetimeExpiredAt) return;\n\n // Cleanup any existing toast before showing new one\n cleanupToast();\n\n const remainingSeconds = calculateRemainingSeconds();\n if (remainingSeconds <= 0) return;\n\n setCountdownToastId(\n countdownToast.create({\n id: COUNTDOWN_CONFIG.TOAST_ID,\n description: COUNTDOWN_CONFIG.MESSAGE(remainingSeconds),\n }),\n );\n\n countdownIntervalRef.current = setInterval(\n updateToast,\n COUNTDOWN_CONFIG.UPDATE_INTERVAL,\n );\n }, [lifetimeExpiredAt, calculateRemainingSeconds, updateToast, cleanupToast]);\n\n // Calculate delay for showing toast\n const remainingSeconds = calculateRemainingSeconds();\n const delay = lifetimeExpiredAt\n ? Math.max(0, remainingSeconds - COUNTDOWN_CONFIG.WARNING_THRESHOLD) * 1000\n : null;\n\n // Use useTimeout hook to schedule toast display\n useTimeout(showToast, delay);\n\n // Cleanup effect\n useEffect(() => {\n return cleanupToast;\n }, [cleanupToast]);\n}\n","/**\n * Hook for data export functionality (CSV, TSV, Excel)\n */\n\nimport { useCallback, useMemo } from \"react\";\nimport type { Run } from \"../api\";\nimport { toaster } from \"../components/ui/Toaster\";\nimport {\n type CSVExportOptions,\n copyToClipboard,\n downloadCSV,\n downloadExcel,\n downloadTSV,\n extractCSVData,\n generateCSVFilename,\n supportsCSVExport,\n toCSV,\n toExcelBlob,\n toTSV,\n} from \"../utils\";\n\ninterface UseCSVExportOptions {\n run?: Run;\n /** View options - displayMode is extracted if present (for query_diff views) */\n viewOptions?: Record<string, unknown>;\n}\n\ninterface UseCSVExportResult {\n /** Whether CSV export is available for this run type */\n canExportCSV: boolean;\n /** Copy result data as CSV to clipboard */\n copyAsCSV: () => Promise<void>;\n /** Copy result data as TSV to clipboard (pastes into spreadsheets) */\n copyAsTSV: () => Promise<void>;\n /** Download result data as CSV file */\n downloadAsCSV: () => void;\n /** Download result data as TSV file */\n downloadAsTSV: () => void;\n /** Download result data as Excel file */\n downloadAsExcel: () => void;\n}\n\nexport function useCSVExport({\n run,\n viewOptions,\n}: UseCSVExportOptions): UseCSVExportResult {\n const canExportCSV = useMemo(() => {\n if (!run?.type || !run?.result) return false;\n return supportsCSVExport(run.type);\n }, [run?.type, run?.result]);\n\n const getExtractedData = useCallback(() => {\n if (!run?.type || !run?.result) return null;\n\n // Extract display_mode from viewOptions if it exists (for query_diff)\n const displayMode = viewOptions?.display_mode as\n | \"inline\"\n | \"side_by_side\"\n | undefined;\n\n // Extract primary_keys from run params (for query_diff with primary keys)\n const primaryKeys = (run?.params as { primary_keys?: string[] })\n ?.primary_keys;\n\n const exportOptions: CSVExportOptions = {\n displayMode,\n primaryKeys,\n };\n\n return extractCSVData(run.type, run.result, exportOptions);\n }, [run?.type, run?.result, run?.params, viewOptions]);\n\n const getCSVContent = useCallback((): string | null => {\n const data = getExtractedData();\n if (!data) return null;\n return toCSV(data.columns, data.rows);\n }, [getExtractedData]);\n\n const getTSVContent = useCallback((): string | null => {\n const data = getExtractedData();\n if (!data) return null;\n return toTSV(data.columns, data.rows);\n }, [getExtractedData]);\n\n const copyAsCSV = useCallback(async () => {\n const content = getCSVContent();\n if (!content) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for CSV export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n await copyToClipboard(content);\n toaster.create({\n title: \"Copied to clipboard\",\n description: \"CSV data copied successfully\",\n type: \"success\",\n duration: 2000,\n });\n } catch (error) {\n console.error(\"Failed to copy CSV to clipboard:\", error);\n toaster.create({\n title: \"Copy failed\",\n description: \"Failed to copy to clipboard\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getCSVContent]);\n\n const downloadAsCSV = useCallback(() => {\n const content = getCSVContent();\n if (!content) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for CSV export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n const filename = generateCSVFilename(\n run?.type ?? \"\",\n run?.params as Record<string, unknown>,\n );\n downloadCSV(content, filename);\n toaster.create({\n title: \"Downloaded\",\n description: filename,\n type: \"success\",\n duration: 3000,\n });\n } catch (error) {\n console.error(\"Failed to download CSV file:\", error);\n toaster.create({\n title: \"Download failed\",\n description: \"Failed to download CSV file\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getCSVContent, run]);\n\n const copyAsTSV = useCallback(async () => {\n const content = getTSVContent();\n if (!content) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n await copyToClipboard(content);\n toaster.create({\n title: \"Copied to clipboard\",\n description: \"Text data copied — paste into any spreadsheet\",\n type: \"success\",\n duration: 2000,\n });\n } catch (error) {\n console.error(\"Failed to copy TSV to clipboard:\", error);\n toaster.create({\n title: \"Copy failed\",\n description: \"Failed to copy to clipboard\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getTSVContent]);\n\n const downloadAsTSV = useCallback(() => {\n const content = getTSVContent();\n if (!content) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n const filename = generateCSVFilename(\n run?.type ?? \"\",\n run?.params as Record<string, unknown>,\n ).replace(/\\.csv$/, \".tsv\");\n downloadTSV(content, filename);\n toaster.create({\n title: \"Downloaded\",\n description: filename,\n type: \"success\",\n duration: 3000,\n });\n } catch (error) {\n console.error(\"Failed to download TSV file:\", error);\n toaster.create({\n title: \"Download failed\",\n description: \"Failed to download TSV file\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getTSVContent, run]);\n\n const downloadAsExcel = useCallback(async () => {\n const data = getExtractedData();\n if (!data) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n const blob = await toExcelBlob(data.columns, data.rows);\n const filename = generateCSVFilename(\n run?.type ?? \"\",\n run?.params as Record<string, unknown>,\n ).replace(/\\.csv$/, \".xlsx\");\n downloadExcel(blob, filename);\n toaster.create({\n title: \"Downloaded\",\n description: filename,\n type: \"success\",\n duration: 3000,\n });\n } catch (error) {\n console.error(\"Failed to download Excel file:\", error);\n toaster.create({\n title: \"Download failed\",\n description: \"Failed to download Excel file\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getExtractedData, run]);\n\n return {\n canExportCSV,\n copyAsCSV,\n copyAsTSV,\n downloadAsCSV,\n downloadAsExcel,\n downloadAsTSV,\n };\n}\n","import Box from \"@mui/material/Box\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Link from \"@mui/material/Link\";\nimport Stack from \"@mui/material/Stack\";\nimport { useState } from \"react\";\nimport { LuExternalLink } from \"react-icons/lu\";\nimport { toaster } from \"../components/ui/Toaster\";\n\nfunction ReactionFeedback({\n description,\n onLike,\n onDislike,\n onClickLink,\n externalLink,\n externalLinkText,\n}: {\n description: string;\n onLike: () => void;\n onDislike: () => void;\n onClickLink: () => void;\n externalLink?: string;\n externalLinkText?: string;\n}) {\n return (\n <Box\n sx={{\n display: \"flex\",\n gap: 4,\n justifyContent: \"center\",\n alignContent: \"center\",\n alignItems: \"center\",\n }}\n >\n {description}\n <IconButton\n aria-label=\"thumbs up\"\n onClick={onLike}\n sx={{ width: \"32px\", height: \"32px\" }}\n >\n <Box component=\"img\" src=\"/imgs/feedback/thumbs-up.png\" alt=\"like\" />\n </IconButton>\n <IconButton\n aria-label=\"thumbs down\"\n onClick={onDislike}\n sx={{ width: \"32px\", height: \"32px\" }}\n >\n <Box\n component=\"img\"\n src=\"/imgs/feedback/thumbs-down.png\"\n alt=\"dislike\"\n />\n </IconButton>\n {externalLink && externalLinkText && (\n <Link\n href={externalLink}\n target=\"_blank\"\n onClick={onClickLink}\n sx={{ textDecoration: \"underline\" }}\n >\n {externalLinkText} <LuExternalLink />\n </Link>\n )}\n </Box>\n );\n}\n\nexport function useFeedbackCollectionToast(options: {\n feedbackId: string;\n description: string;\n onFeedbackSubmit: (feedback: string) => void;\n externalLink?: string;\n externalLinkText?: string;\n}) {\n const {\n feedbackId,\n description,\n onFeedbackSubmit,\n externalLink,\n externalLinkText,\n } = options;\n const [toastId, setToastId] = useState<string | undefined>(undefined);\n\n function feedBackCollectionToast(skipBypassFeedback = false) {\n const isSkipFeedback = localStorage.getItem(feedbackId);\n if (toastId != null) {\n // Don't show the toast again if it's already active\n return;\n }\n if (isSkipFeedback === \"true\" && !skipBypassFeedback) {\n return;\n }\n\n setToastId(\n toaster.create({\n id: feedbackId,\n duration: undefined,\n type: \"success\",\n description: (\n <Stack direction=\"row\">\n <ReactionFeedback\n description={description}\n onLike={() => {\n onFeedbackSubmit(\"like\");\n toaster.dismiss(feedbackId);\n localStorage.setItem(feedbackId, \"true\");\n }}\n onDislike={() => {\n onFeedbackSubmit(\"dislike\");\n toaster.dismiss(feedbackId);\n localStorage.setItem(feedbackId, \"true\");\n }}\n externalLink={externalLink}\n externalLinkText={externalLinkText}\n onClickLink={() => {\n onFeedbackSubmit(\"link\");\n }}\n />\n </Stack>\n ),\n }),\n );\n }\n\n return {\n feedbackToast: feedBackCollectionToast,\n closeToast: () => {\n if (toastId) toaster.dismiss(toastId);\n },\n };\n}\n","/**\n * @recce-migration NOT_APPLICABLE\n *\n * This hook is specific to Recce OSS and should not be migrated to @datarecce/ui.\n *\n * Reason: Onboarding guide toasts are tied to OSS-specific feature flags and\n * user onboarding flows. Cloud has different onboarding patterns.\n *\n * If this changes in the future, consider:\n * - Creating a generic toast system in @datarecce/ui\n * - Keeping onboarding logic in host applications\n */\n\nimport { useState } from \"react\";\nimport { toaster } from \"../components/ui/Toaster\";\n\nexport function useGuideToast(options: {\n guideId: string;\n description: string;\n externalLink?: string;\n externalLinkText?: string;\n onExternalLinkClick?: () => void;\n}) {\n const [toastId, setToastId] = useState<string | undefined>(undefined);\n const { guideId, description, externalLinkText, onExternalLinkClick } =\n options;\n\n function guideToast() {\n if (toastId != null) {\n // Don't show the toast again if it's already active\n return;\n }\n\n setToastId(\n toaster.create({\n id: guideId,\n duration: 3000,\n type: \"success\",\n description: description,\n action: {\n label: externalLinkText ?? \"link\",\n onClick: () => {\n if (onExternalLinkClick) {\n onExternalLinkClick();\n }\n },\n },\n }),\n );\n }\n\n return {\n guideToast: guideToast,\n closeGuideToast: () => {\n if (toastId) toaster.dismiss(toastId);\n },\n };\n}\n","/**\n * @file useModelColumns.tsx\n * @description Hook to fetch and manage column information for a model.\n * Combines data from lineage graph context with API calls for column details.\n */\n\nimport type { AxiosInstance } from \"axios\";\nimport _ from \"lodash\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { getModelInfo, type NodeColumnData } from \"../api\";\nimport {\n type LineageGraphNode,\n useLineageGraphContext,\n} from \"../contexts/lineage\";\nimport { useApiConfigOptional } from \"../providers\";\n\n/**\n * Extract columns from a lineage graph node.\n * Combines base and current columns using union logic.\n */\nexport function extractColumns(node: LineageGraphNode): NodeColumnData[] {\n function getColumns(\n nodeData:\n | { columns?: Record<string, NodeColumnData | undefined> }\n | undefined,\n ): NodeColumnData[] {\n return nodeData?.columns\n ? Object.values(nodeData.columns).filter(\n (c): c is NodeColumnData => c != null,\n )\n : [];\n }\n\n const baseColumns = getColumns(node.data.data.base);\n const currentColumns = getColumns(node.data.data.current);\n\n return unionColumns(baseColumns, currentColumns);\n}\n\n/**\n * Create a union of base and current columns by name.\n * Columns from current take precedence if both exist.\n */\nexport function unionColumns(\n baseColumns: NodeColumnData[],\n currentColumns: NodeColumnData[],\n): NodeColumnData[] {\n const union: NodeColumnData[] = [];\n baseColumns.forEach((column) => {\n if (!union.some((c) => c.name === column.name)) {\n union.push(column);\n }\n });\n currentColumns.forEach((column) => {\n if (!union.some((c) => c.name === column.name)) {\n union.push(column);\n }\n });\n\n return union;\n}\n\n/**\n * Return type for the useModelColumns hook\n */\nexport interface UseModelColumnsReturn {\n columns: NodeColumnData[];\n primaryKey: string | undefined;\n isLoading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook to fetch model column information.\n *\n * This hook combines data from the lineage graph context (if available)\n * with API calls to get detailed column information for a model.\n *\n * @param model - The model name to fetch columns for\n * @param client - Axios instance for API calls (optional - will use context if not provided)\n * @returns Object with columns, primaryKey, isLoading, and error states\n *\n * @example\n * ```tsx\n * const { columns, primaryKey, isLoading, error } = useModelColumns(modelName);\n *\n * if (isLoading) return <Loading />;\n * if (error) return <Error message={error.message} />;\n *\n * return <ColumnList columns={columns} primaryKey={primaryKey} />;\n * ```\n */\nexport function useModelColumns(\n model: string | undefined,\n client?: AxiosInstance,\n): UseModelColumnsReturn {\n const { lineageGraph } = useLineageGraphContext();\n const apiConfig = useApiConfigOptional();\n\n // Use provided client or fall back to context client\n const axiosClient = client ?? apiConfig?.apiClient;\n\n const node = _.find(lineageGraph?.nodes, {\n data: {\n name: model,\n },\n });\n\n const nodeColumns = useMemo(() => {\n return node ? extractColumns(node) : [];\n }, [node]);\n\n const [columns, setColumns] = useState<NodeColumnData[]>([]);\n const [primaryKey, setPrimaryKey] = useState<string>();\n const [isLoading, setIsLoading] = useState<boolean>(true);\n const [error, setError] = useState<Error | null>(null);\n const [prevNodeColumns, setPrevNodeColumns] = useState<NodeColumnData[]>([]);\n const [prevNodeId, setPrevNodeId] = useState(node?.id);\n\n const nodePrimaryKey = node ? node.data.data.current?.primary_key : undefined;\n\n const fetchData = useCallback(async () => {\n if (!node || !axiosClient) {\n return;\n }\n try {\n const data = await getModelInfo(node.id, axiosClient);\n const modelInfo = data.model;\n if (!modelInfo.base.columns || !modelInfo.current.columns) {\n setColumns([]);\n return;\n }\n setPrimaryKey(modelInfo.current.primary_key);\n const baseColumns = Object.values(modelInfo.base.columns);\n const currentColumns = Object.values(modelInfo.current.columns);\n setColumns(unionColumns(baseColumns, currentColumns));\n } catch (err) {\n setError(err as Error);\n }\n }, [node, axiosClient]);\n\n // Adjust state during render when node changes\n if (nodeColumns !== prevNodeColumns || node?.id !== prevNodeId) {\n setPrevNodeColumns(nodeColumns);\n setPrevNodeId(node?.id);\n\n if (nodeColumns.length > 0) {\n setColumns(nodeColumns);\n setPrimaryKey(nodePrimaryKey);\n setIsLoading(false);\n } else if (node?.id === undefined) {\n setColumns([]);\n setIsLoading(false);\n }\n // Note: fetchData case is handled separately in effect below\n }\n\n // Fetch data effect - only runs when we need to fetch\n useEffect(() => {\n if (nodeColumns.length === 0 && node?.id !== undefined) {\n fetchData().catch((e: unknown) => {\n // error is already handled in fetchData()\n console.error(e);\n });\n setIsLoading(false);\n }\n }, [fetchData, node?.id, nodeColumns]);\n\n return { columns, primaryKey, isLoading, error };\n}\n\nexport default useModelColumns;\n","import { useQuery } from \"@tanstack/react-query\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { Run } from \"../api\";\nimport { cacheKeys, cancelRun, runTypeHasRef, waitRun } from \"../api\";\nimport type { RegistryEntry } from \"../components/run\";\nimport { findByRunType } from \"../components/run\";\nimport { useRunsAggregated } from \"../contexts\";\nimport { useApiConfig } from \"./useApiConfig\";\n\nexport interface UseRunResult {\n run?: Run;\n aborting: boolean;\n isRunning: boolean;\n error: Error | null;\n onCancel: () => Promise<void>;\n RunResultView?: RegistryEntry[\"RunResultView\"] | undefined;\n}\n\nexport const useRun = (runId?: string): UseRunResult => {\n const { apiClient } = useApiConfig();\n // Initialize to true when runId is provided - a newly submitted run is typically running.\n // The first successful fetch will update this to false if the run has already completed.\n const [isRunning, setIsRunning] = useState(!!runId);\n const [aborting, setAborting] = useState(false);\n const [, refetchRunsAggregated] = useRunsAggregated();\n\n // Track the run ID that has been detected as complete to prevent re-triggering\n // This ref persists across renders and prevents the race condition where\n // React Query's fast polling (50ms) fires before state updates propagate\n const completedRunIdRef = useRef<string | null>(null);\n\n // Reset isRunning to true when runId changes (new run submitted)\n // This ensures polling starts immediately for newly submitted runs\n useEffect(() => {\n if (runId) {\n setIsRunning(true);\n // Clear the completed ref so we can detect completion for the new run\n completedRunIdRef.current = null;\n }\n }, [runId]);\n\n const { error, data: run } = useQuery({\n queryKey: cacheKeys.run(runId ?? \"\"),\n queryFn: async () => {\n // Cast from library Run to OSS Run for discriminated union support\n return (await waitRun(runId ?? \"\", isRunning ? 2 : 0, apiClient)) as Run;\n },\n enabled: !!runId,\n refetchInterval: isRunning ? 50 : false,\n retry: false,\n });\n\n // Control polling based on run completion status\n // Uses useEffect instead of state-during-render to avoid race conditions\n // with React Query's fast polling interval (50ms)\n useEffect(() => {\n if (!run) return;\n\n // Normalize status to lowercase for case-insensitive comparison\n // Backend may return \"Running\" (capitalized) or \"running\" (lowercase)\n const normalizedStatus = run.status?.toLowerCase();\n\n // Check if run has completed (has result or error)\n const isComplete = !!(error || run.result || run.error);\n const isStatusComplete = normalizedStatus && normalizedStatus !== \"running\";\n\n if (isComplete || isStatusComplete) {\n // Only trigger state update once per completed run\n if (completedRunIdRef.current !== run.run_id) {\n completedRunIdRef.current = run.run_id;\n setIsRunning(false);\n }\n } else if (normalizedStatus === \"running\") {\n // Run is still in progress\n completedRunIdRef.current = null;\n setIsRunning(true);\n }\n }, [run, error]);\n\n // Side effect: refetch aggregated runs when row count runs complete\n useEffect(() => {\n if (\n (error || run?.result || run?.error) &&\n (run?.type === \"row_count_diff\" || run?.type === \"row_count\")\n ) {\n refetchRunsAggregated?.();\n }\n }, [run, error, refetchRunsAggregated]);\n\n const onCancel = useCallback(async () => {\n setAborting(true);\n if (!runId) {\n return;\n }\n\n await cancelRun(runId, apiClient);\n return;\n }, [runId, apiClient]);\n\n let RunResultView: RegistryEntry[\"RunResultView\"] | undefined;\n if (run && runTypeHasRef(run.type)) {\n RunResultView = findByRunType(run.type)\n .RunResultView as RegistryEntry[\"RunResultView\"];\n }\n\n return {\n run,\n isRunning,\n aborting,\n error,\n onCancel,\n RunResultView,\n };\n};\n","\"use client\";\n\nimport { useTheme as useMuiTheme } from \"@mui/material/styles\";\nimport { useEffect, useState } from \"react\";\nimport { useRecceThemeOptional } from \"../providers/contexts/ThemeContext\";\nimport { colors } from \"../theme/colors\";\nimport { useIsDark } from \"./useIsDark\";\n\n/**\n * Theme-aware color utility hook\n *\n * Returns a consistent set of colors based on the current theme mode.\n *\n * **Dual-Context Support:**\n * This hook works in two contexts:\n * 1. **With RecceProvider** (recce-cloud-infra): Uses ThemeContext for theme detection\n * 2. **Without RecceProvider** (Recce OSS with next-themes): Falls back to useIsDark\n * which uses DOM class detection (.dark on <html>)\n *\n * This allows @datarecce/ui components to work in both environments without\n * requiring the host application to wrap everything in RecceProvider.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { isDark, background, text, border } = useThemeColors();\n *\n * return (\n * <Box sx={{\n * bgcolor: background.paper,\n * color: text.primary,\n * borderColor: border.default,\n * }}>\n * Content\n * </Box>\n * );\n * }\n * ```\n */\nexport function useThemeColors() {\n const muiTheme = useMuiTheme();\n // Try context first (returns null if not in RecceProvider)\n const themeContext = useRecceThemeOptional();\n // Fallback to useIsDark which has DOM class detection\n const isDarkFallback = useIsDark();\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n // Determine dark mode: prefer context if available, otherwise use fallback\n const isDark = mounted\n ? themeContext\n ? themeContext.resolvedMode === \"dark\"\n : isDarkFallback\n : false;\n\n return {\n /** Whether the current theme is dark mode */\n isDark,\n\n /** MUI theme object for direct access when needed */\n theme: muiTheme,\n\n /** Background colors */\n background: {\n /** Default page background */\n default: isDark ? colors.neutral[900] : colors.white,\n /** Paper/card background */\n paper: isDark ? colors.neutral[800] : colors.white,\n /** Subtle background for slight elevation (e.g., hover states, inputs) */\n subtle: isDark ? colors.neutral[800] : colors.neutral[50],\n /** Emphasized background for higher contrast areas */\n emphasized: isDark ? colors.neutral[700] : colors.neutral[100],\n },\n\n /** Text colors */\n text: {\n /** Primary text color */\n primary: isDark ? colors.neutral[50] : colors.neutral[900],\n /** Secondary/muted text color */\n secondary: isDark ? colors.neutral[400] : colors.neutral[600],\n /** Disabled text color */\n disabled: isDark ? colors.neutral[500] : colors.neutral[400],\n /** Inverted text (for use on dark backgrounds in light mode, etc.) */\n inverted: isDark ? colors.neutral[900] : colors.neutral[50],\n },\n\n /** Border colors */\n border: {\n /** Light border for subtle separations */\n light: isDark ? colors.neutral[700] : colors.neutral[200],\n /** Default border color */\n default: isDark ? colors.neutral[600] : colors.neutral[300],\n /** Strong border for emphasis */\n strong: isDark ? colors.neutral[500] : colors.neutral[400],\n },\n\n /** Status/semantic colors */\n status: {\n /** Added/success backgrounds */\n added: {\n bg: isDark ? colors.green[900] : colors.green[100],\n text: isDark ? colors.neutral[50] : colors.neutral[900],\n },\n /** Removed/error backgrounds */\n removed: {\n bg: isDark ? colors.red[950] : colors.red[200],\n text: isDark ? colors.neutral[50] : colors.neutral[900],\n },\n /** Modified/warning backgrounds */\n modified: {\n bg: isDark ? colors.yellow[900] : colors.amber[100],\n text: isDark ? colors.neutral[50] : colors.neutral[900],\n },\n },\n\n /** Interactive element colors */\n interactive: {\n /** Hover state background */\n hover: isDark ? colors.neutral[700] : colors.neutral[100],\n /** Active/pressed state background */\n active: isDark ? colors.neutral[600] : colors.neutral[200],\n /** Focus ring color */\n focus: colors.iochmara[500],\n },\n };\n}\n\nexport type ThemeColors = ReturnType<typeof useThemeColors>;\n","import axios, { type AxiosInstance } from \"axios\";\nimport { PUBLIC_API_URL } from \"../const\";\n\nexport interface ConnectToCloud {\n connection_url: string;\n}\n\nconst defaultApiClient = axios.create({\n baseURL: PUBLIC_API_URL,\n});\n\nexport async function connectToCloud(\n client: AxiosInstance = defaultApiClient,\n): Promise<ConnectToCloud> {\n const data = await client.post<ConnectToCloud>(\"/api/connect\");\n return data.data;\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport Link from \"@mui/material/Link\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport Cookies from \"js-cookie\";\nimport { Dispatch, ReactNode, SetStateAction, useState } from \"react\";\nimport { LuExternalLink } from \"react-icons/lu\";\nimport { useRecceInstanceContext } from \"../../contexts\";\nimport { useApiConfig } from \"../../hooks\";\nimport { connectToCloud } from \"../../lib/api/connectToCloud\";\n\ntype AuthState = \"authenticating\" | \"pending\" | \"canceled\" | \"ignored\";\n\ninterface AuthModalProps {\n handleParentClose?: Dispatch<SetStateAction<boolean>>;\n parentOpen?: boolean;\n ignoreCookie?: boolean;\n variant?: \"auth\" | \"enable-share\" | \"user-profile\";\n}\n\nexport default function AuthModal({\n handleParentClose,\n parentOpen = false,\n ignoreCookie = false,\n variant = \"auth\",\n}: AuthModalProps): ReactNode {\n const { authed } = useRecceInstanceContext();\n const { apiClient } = useApiConfig();\n const [open, setOpen] = useState(parentOpen || !authed);\n\n // Cookie handling only for auth variant\n const authStateCookieValue = (Cookies.get(\"authState\") ??\n \"pending\") as AuthState;\n const [authState, setAuthState] = useState<AuthState>(\n ignoreCookie ? \"pending\" : authStateCookieValue,\n );\n\n if (authState === \"ignored\" && !ignoreCookie) {\n return null;\n }\n\n if (authed) {\n return null;\n }\n\n // Content configuration based on variant\n const contents = {\n auth: {\n title: \"Configure Cloud Token\",\n action: \"Get token and configure\",\n },\n \"enable-share\": {\n title: \"Enable Sharing with Cloud\",\n action: \"Enable sharing\",\n },\n \"user-profile\": {\n title: \"Configure Cloud Token\",\n action: \"Get token and configure\",\n },\n };\n\n const content = contents[variant];\n\n const handleClose = () => {\n setOpen(false);\n if (handleParentClose) {\n handleParentClose(false);\n }\n };\n\n return (\n <MuiDialog\n open={open}\n onClose={handleClose}\n maxWidth=\"sm\"\n fullWidth\n slotProps={{\n paper: { sx: { borderRadius: \"1rem\" } },\n }}\n >\n {authState !== \"authenticating\" && (\n <DialogTitle sx={{ textAlign: \"center\", fontSize: \"1.5rem\" }}>\n {content.title}\n </DialogTitle>\n )}\n {authState !== \"authenticating\" ? (\n <>\n <DialogContent className=\"space-y-2 font-light\">\n <Typography>\n To enable sharing, get your token from Recce Cloud and launch your\n local instance with it.\n </Typography>\n <ul className=\"list-inside list-disc\">\n <li>Share your instance with teammates via Recce Cloud.</li>\n <li>\n Your instance will be securely and freely hosted for sharing.\n </li>\n {variant === \"auth\" && (\n <li>This step is recommended but optional.</li>\n )}\n </ul>\n <Box sx={{ display: \"flex\", gap: 1 }}>\n More directions\n <Link\n underline=\"always\"\n sx={{\n color: \"primary.main\",\n \"&:focus\": { outline: \"none\" },\n }}\n href=\"https://cloud.datarecce.io/connect-to-cloud\"\n target=\"_blank\"\n >\n here <LuExternalLink style={{ display: \"inline\" }} />\n </Link>\n </Box>\n </DialogContent>\n <DialogActions sx={{ flexDirection: \"column\", gap: 1, px: 3, pb: 3 }}>\n <Button\n fullWidth\n color=\"brand\"\n variant=\"contained\"\n sx={{ borderRadius: 2, fontWeight: 500 }}\n onClick={async () => {\n setAuthState(\"authenticating\");\n const { connection_url } = await connectToCloud(apiClient);\n // Open the connection URL in a new tab\n window.open(connection_url, \"_blank\");\n }}\n >\n {content.action} <LuExternalLink style={{ marginLeft: 4 }} />\n </Button>\n <Button\n fullWidth\n color=\"neutral\"\n variant=\"text\"\n size=\"small\"\n sx={{ borderRadius: 2, fontWeight: 500 }}\n onClick={handleClose}\n >\n {variant === \"auth\" ? \"Skip\" : \"Cancel\"}\n </Button>\n {variant === \"auth\" && (\n <Button\n fullWidth\n variant=\"text\"\n size=\"small\"\n sx={{ borderRadius: 2, fontWeight: 500, color: \"text.primary\" }}\n onClick={() => {\n Cookies.set(\"authState\", \"ignored\", {\n expires: 30,\n });\n setAuthState(\"ignored\");\n handleClose();\n }}\n >\n Snooze for 30 days\n </Button>\n )}\n </DialogActions>\n </>\n ) : (\n <>\n <DialogContent className=\"space-y-2 self-center font-light\">\n <Stack spacing={2} alignItems=\"center\" sx={{ pt: \"1rem\" }}>\n <Box\n component=\"img\"\n sx={{ height: \"6rem\", objectFit: \"contain\", mx: \"auto\", mb: 1 }}\n src=\"/imgs/reload-image.svg\"\n alt=\"Reload\"\n />\n <Typography sx={{ fontSize: \"1.5rem\", fontWeight: 500 }}>\n Reload to Finish\n </Typography>\n <Typography>\n Reload to complete connection to Recce Cloud\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ px: 3, pb: 3 }}>\n <Button\n fullWidth\n color=\"brand\"\n variant=\"contained\"\n onClick={() => {\n window.location.reload();\n }}\n >\n Reload\n </Button>\n </DialogActions>\n </>\n )}\n </MuiDialog>\n );\n}\n"],"mappings":";mrLAmCA,SAAgB,GAAY,CAC1B,WACA,cACA,oBAAoB,IACD,CACnB,GAAM,CAAE,iBAAkBA,IAAU,CAapC,OAVA,MAAgB,EACD,GAAe,KACf,OACX,SAAS,gBAAgB,UAAU,IAAI,OAAO,CAE9C,SAAS,gBAAgB,UAAU,OAAO,OAAO,EAElD,CAAC,EAAa,EAAc,CAAC,CAI9B,EAACC,GAAD,CAAyB,kBAAzB,CACG,GAAqB,EAAC,GAAD,EAAe,CAAA,CACpC,EACgB,GCrBvB,MAAM,GAAe,GALoB,CACvC,OAAQ,EAAE,CACV,UAAW,GACZ,CAEmE,CACpE,GAAa,YAAc,oBAsB3B,SAAgB,GAAc,CAC5B,WACA,SAAS,EAAE,CACX,YAAY,GACZ,QACA,kBACA,gBACA,gBACA,gBACA,gBACA,kBACA,gBAEA,wBACA,4BACqB,CAErB,IAAM,EAA0B,GAAmB,EAC7C,EAAwB,GAAiB,EAEzC,EAAe,OACZ,CACL,SACA,YACA,QAEA,gBAAiB,EACjB,cAAe,EACf,gBACA,gBACA,gBACA,kBACA,gBAEA,sBAAuB,EACvB,yBAA0B,EAC3B,EACD,CACE,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACD,CACF,CAED,OACE,EAAC,GAAa,SAAd,CAAuB,MAAO,EAC3B,WACqB,CAAA,CAI5B,SAAgB,IAAoC,CAClD,OAAO,GAAW,GAAa,CCzEjC,MAAM,GAAe,GALoB,CACvC,IAAK,GACL,YAAa,GACd,CAEmE,CACpE,GAAa,YAAc,oBAyB3B,SAAgB,GAAc,CAC5B,WACA,MAAM,GACN,cAAc,GACd,QACA,aACA,gBACA,cACA,YACA,WAEA,WACA,cACA,cACA,iBACA,kBACA,mBACA,eACA,mBACqB,CACrB,IAAM,EAAe,OACZ,CACL,MACA,cACA,QACA,aACA,gBACA,cACA,YACA,WAEA,WACA,cACA,cACA,iBACA,kBACA,mBACA,eACA,kBACD,EACD,CACE,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACD,CACF,CAED,OACE,EAAC,GAAa,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"}
|
|
1
|
+
{"version":3,"file":"AuthModal-B_SYkx-_.js","names":["useTheme","MuiThemeProvider","changeStatusColors","MuiTooltip","getLanguageExtension","MuiTooltip","Popover","ChartJS","Tooltip","Chart","ChartJS","ChartTooltip","SyntaxHighlighter","Alert","toDataGrid","toDataDiffGrid","toValueDiffGrid","BaseRunList","UIRunModal","MuiLink","BaseRunResultPane","ErrorBoundary","SentryErrorBoundary","BaseRunView","useUIRecceActionContext","useCopyToClipboard","useMuiTheme"],"sources":["../src/components/ui/mui-provider.tsx","../src/providers/contexts/CheckContext.tsx","../src/providers/contexts/QueryContext.tsx","../src/hooks/CheckContextAdapter.tsx","../src/components/ui/DataTypeIcon/classifyType.ts","../src/components/ui/DataTypeIcon/icons.tsx","../src/components/ui/DataTypeIcon/tooltipText.ts","../src/components/ui/DataTypeIcon/index.tsx","../src/components/lineage/columns/LineageColumnNode.tsx","../src/components/lineage/styles.tsx","../src/components/lineage/edges/LineageEdge.tsx","../src/components/ui/ChangedOnlyCheckbox.tsx","../src/components/ui/ToggleSwitch.tsx","../src/components/ui/DiffDisplayModeSwitch.tsx","../src/components/ui/DropdownValuesInput.tsx","../src/components/histogram/HistogramDiffForm.tsx","../src/components/lineage/legend/LineageLegend.tsx","../src/components/lineage/nodes/LineageNode.tsx","../src/components/check/CheckActions.tsx","../src/components/check/CheckBreadcrumb.tsx","../src/components/check/CheckCard.tsx","../src/components/check/CheckDescription.tsx","../src/components/check/CheckDetail.tsx","../src/components/check/CheckEmptyState.tsx","../src/components/check/CheckList.tsx","../src/components/lineage/LineageCanvas.tsx","../src/components/lineage/LineageView.tsx","../src/components/check/LineageDiffView.tsx","../src/components/editor/CodeEditor.tsx","../src/components/check/PresetCheckTemplateView.tsx","../src/hooks/useApiConfig.ts","../src/hooks/useAvatar.ts","../src/components/check/timeline/CommentInput.tsx","../src/components/check/timeline/TimelineEvent.tsx","../src/components/check/utils.ts","../src/components/run/RunStatusBadge.tsx","../src/components/run/RunList.tsx","../src/components/run/RunProgress.tsx","../src/components/run/RunToolbar.tsx","../src/components/data/HistogramChart.tsx","../src/components/data/ScreenshotDataGrid.tsx","../src/components/data/TopKBarChart.tsx","../src/components/editor/DiffEditor.tsx","../src/components/ui/EmptyState.tsx","../src/components/ui/ExternalLinkConfirmDialog.tsx","../src/components/ui/MarkdownContent.tsx","../src/components/ui/ScreenshotBox.tsx","../src/components/ui/SplitPane.tsx","../src/components/ui/Split.tsx","../src/components/result/createResultView.tsx","../src/primitives.ts","../src/components/histogram/HistogramResultView.tsx","../src/components/schema/ColumnNameCell.tsx","../src/components/ui/dataGrid/schemaCells.tsx","../src/lib/dataGrid/generators/toSchemaDataGrid.ts","../src/components/ui/dataGrid/valueDiffCells.tsx","../src/components/ui/dataGrid/generators/toValueDataGrid.ts","../src/components/ui/dataGrid/dataGridFactory.tsx","../src/components/profile/ProfileDiffForm.tsx","../src/components/profile/ProfileResultView.tsx","../src/components/query/QueryDiffResultView.tsx","../src/components/query/QueryResultView.tsx","../src/components/rowcount/RowCountResultView.tsx","../src/components/top-k/TopKDiffForm.tsx","../src/components/top-k/TopKDiffResultView.tsx","../src/components/valuediff/ValueDiffDetailResultView.tsx","../src/components/valuediff/ValueDiffForm.tsx","../src/components/valuediff/ValueDiffResultView.tsx","../src/components/run/registry.ts","../src/components/run/RunListOss.tsx","../src/components/run/RunModal.tsx","../src/components/run/RunModalOss.tsx","../src/components/run/RunView.tsx","../src/components/run/RunResultPane.tsx","../src/components/onboarding-guide/Notification.tsx","../src/components/query/SqlEditor.tsx","../src/components/run/RunResultPaneOss.tsx","../src/components/errorboundary/ErrorBoundary.tsx","../src/components/run/RunViewOss.tsx","../src/hooks/useMultiNodesAction.ts","../src/hooks/useValueDiffAlertDialog.tsx","../src/components/lineage/ServerDisconnectedModalContent.tsx","../src/hooks/LineageGraphAdapter.tsx","../src/hooks/QueryContextAdapter.tsx","../src/hooks/RecceActionAdapter.tsx","../src/hooks/RecceShareStateContext.tsx","../src/hooks/RecceContextProvider.tsx","../src/hooks/ScreenShot.tsx","../src/hooks/useCheckEvents.ts","../src/hooks/useCountdownToast.tsx","../src/hooks/useCSVExport.ts","../src/hooks/useFeedbackCollectionToast.tsx","../src/hooks/useGuideToast.tsx","../src/hooks/useModelColumns.tsx","../src/hooks/useRun.tsx","../src/hooks/useThemeColors.ts","../src/lib/api/connectToCloud.ts","../src/components/app/AuthModal.tsx"],"sourcesContent":["\"use client\";\n\nimport CssBaseline from \"@mui/material/CssBaseline\";\nimport { ThemeProvider as MuiThemeProvider } from \"@mui/material/styles\";\nimport { useTheme } from \"next-themes\";\nimport { type ReactNode, useEffect } from \"react\";\nimport { theme } from \"../../theme\";\n\ninterface MuiProviderProps {\n children: ReactNode;\n /**\n * Force a specific theme mode. If not provided, follows system/user preference.\n */\n forcedTheme?: \"light\" | \"dark\";\n /**\n * Whether to include MUI's CssBaseline for consistent baseline styles.\n * Disabled by default to avoid conflicts with Chakra/Tailwind during migration.\n */\n enableCssBaseline?: boolean;\n}\n\n/**\n * MUI Theme Provider for Recce\n *\n * This provider integrates MUI theming with the existing next-themes\n * color mode system used by Chakra UI. MUI 7 CSS Variables mode is used,\n * which responds to the `.dark` class on document.documentElement.\n *\n * Usage:\n * ```tsx\n * <MuiProvider>\n * <MuiButton>Click me</MuiButton>\n * </MuiProvider>\n * ```\n */\nexport function MuiProvider({\n children,\n forcedTheme,\n enableCssBaseline = false,\n}: MuiProviderProps) {\n const { resolvedTheme } = useTheme();\n\n // Toggle .dark class on document.documentElement for CSS Variables mode\n useEffect(() => {\n const mode = forcedTheme ?? resolvedTheme;\n if (mode === \"dark\") {\n document.documentElement.classList.add(\"dark\");\n } else {\n document.documentElement.classList.remove(\"dark\");\n }\n }, [forcedTheme, resolvedTheme]);\n\n // Use single theme - CSS Variables mode handles light/dark via .dark class\n return (\n <MuiThemeProvider theme={theme}>\n {enableCssBaseline && <CssBaseline />}\n {children}\n </MuiThemeProvider>\n );\n}\n\nexport default MuiProvider;\n","\"use client\";\n\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\nexport interface Check {\n check_id: string;\n name: string;\n type: string;\n description?: string;\n is_checked?: boolean;\n}\n\nexport interface CheckContextType {\n checks: Check[];\n isLoading: boolean;\n error?: string;\n selectedCheckId?: string;\n onSelectCheck?: (checkId: string) => void;\n onCreateCheck?: (check: Partial<Check>) => Promise<Check>;\n onUpdateCheck?: (checkId: string, updates: Partial<Check>) => Promise<Check>;\n onDeleteCheck?: (checkId: string) => Promise<void>;\n onReorderChecks?: (sourceIndex: number, destIndex: number) => Promise<void>;\n refetchChecks?: () => void;\n\n // OSS aliases for backward compatibility\n /** @remarks Alias of selectedCheckId (OSS backward compatibility). */\n latestSelectedCheckId?: string;\n /** @remarks Alias of onSelectCheck (OSS backward compatibility). */\n setLatestSelectedCheckId?: (checkId: string) => void;\n}\n\nconst defaultContext: CheckContextType = {\n checks: [],\n isLoading: false,\n};\n\nconst CheckContext = createContext<CheckContextType>(defaultContext);\nCheckContext.displayName = \"RecceCheckContext\";\n\nexport interface CheckProviderProps {\n children: ReactNode;\n checks?: Check[];\n isLoading?: boolean;\n error?: string;\n selectedCheckId?: string;\n onSelectCheck?: (checkId: string) => void;\n onCreateCheck?: (check: Partial<Check>) => Promise<Check>;\n onUpdateCheck?: (checkId: string, updates: Partial<Check>) => Promise<Check>;\n onDeleteCheck?: (checkId: string) => Promise<void>;\n onReorderChecks?: (sourceIndex: number, destIndex: number) => Promise<void>;\n refetchChecks?: () => void;\n\n // OSS aliases for backward compatibility (prefer canonical names above)\n /** @remarks Alias of selectedCheckId (OSS backward compatibility). */\n latestSelectedCheckId?: string;\n /** @remarks Alias of onSelectCheck (OSS backward compatibility). */\n setLatestSelectedCheckId?: (checkId: string) => void;\n}\n\nexport function CheckProvider({\n children,\n checks = [],\n isLoading = false,\n error,\n selectedCheckId,\n onSelectCheck,\n onCreateCheck,\n onUpdateCheck,\n onDeleteCheck,\n onReorderChecks,\n refetchChecks,\n // OSS aliases (canonical props take precedence)\n latestSelectedCheckId,\n setLatestSelectedCheckId,\n}: CheckProviderProps) {\n // Resolve values: canonical props take precedence over OSS aliases\n const resolvedSelectedCheckId = selectedCheckId ?? latestSelectedCheckId;\n const resolvedOnSelectCheck = onSelectCheck ?? setLatestSelectedCheckId;\n\n const contextValue = useMemo<CheckContextType>(\n () => ({\n checks,\n isLoading,\n error,\n // Canonical properties\n selectedCheckId: resolvedSelectedCheckId,\n onSelectCheck: resolvedOnSelectCheck,\n onCreateCheck,\n onUpdateCheck,\n onDeleteCheck,\n onReorderChecks,\n refetchChecks,\n // OSS aliases (point to same resolved values)\n latestSelectedCheckId: resolvedSelectedCheckId,\n setLatestSelectedCheckId: resolvedOnSelectCheck,\n }),\n [\n checks,\n isLoading,\n error,\n resolvedSelectedCheckId,\n resolvedOnSelectCheck,\n onCreateCheck,\n onUpdateCheck,\n onDeleteCheck,\n onReorderChecks,\n refetchChecks,\n ],\n );\n\n return (\n <CheckContext.Provider value={contextValue}>\n {children}\n </CheckContext.Provider>\n );\n}\n\nexport function useCheckContext(): CheckContextType {\n return useContext(CheckContext);\n}\n","\"use client\";\n\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\nexport interface QueryResult {\n columns: string[];\n data: Record<string, unknown>[];\n rowCount: number;\n}\n\nexport interface QueryContextType {\n // --- @datarecce/ui execution state ---\n sql: string;\n isExecuting: boolean;\n error?: string;\n baseResult?: QueryResult;\n currentResult?: QueryResult;\n onSqlChange?: (sql: string) => void;\n onExecute?: (sql: string) => Promise<void>;\n onCancel?: () => void;\n\n // --- OSS input state (merged for backward compatibility) ---\n /** @remarks Alias of sql (OSS backward compatibility). */\n sqlQuery?: string;\n /** @remarks Alias of onSqlChange (OSS backward compatibility). */\n setSqlQuery?: (sql: string) => void;\n /** Primary key columns for diff matching */\n primaryKeys?: string[];\n /** Setter for primary keys */\n setPrimaryKeys?: (pks: string[] | undefined) => void;\n /** Whether using custom SQL queries vs model-generated */\n isCustomQueries?: boolean;\n /** Setter for isCustomQueries */\n setCustomQueries?: (isCustom: boolean) => void;\n /** Base SQL query for diff comparison */\n baseSqlQuery?: string;\n /** Setter for base SQL query */\n setBaseSqlQuery?: (sql: string) => void;\n}\n\nconst defaultContext: QueryContextType = {\n sql: \"\",\n isExecuting: false,\n};\n\nconst QueryContext = createContext<QueryContextType>(defaultContext);\nQueryContext.displayName = \"RecceQueryContext\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n // --- @datarecce/ui execution state ---\n sql?: string;\n isExecuting?: boolean;\n error?: string;\n baseResult?: QueryResult;\n currentResult?: QueryResult;\n onSqlChange?: (sql: string) => void;\n onExecute?: (sql: string) => Promise<void>;\n onCancel?: () => void;\n\n // --- OSS input state ---\n sqlQuery?: string;\n setSqlQuery?: (sql: string) => void;\n primaryKeys?: string[];\n setPrimaryKeys?: (pks: string[] | undefined) => void;\n isCustomQueries?: boolean;\n setCustomQueries?: (isCustom: boolean) => void;\n baseSqlQuery?: string;\n setBaseSqlQuery?: (sql: string) => void;\n}\n\nexport function QueryProvider({\n children,\n sql = \"\",\n isExecuting = false,\n error,\n baseResult,\n currentResult,\n onSqlChange,\n onExecute,\n onCancel,\n // OSS fields\n sqlQuery,\n setSqlQuery,\n primaryKeys,\n setPrimaryKeys,\n isCustomQueries,\n setCustomQueries,\n baseSqlQuery,\n setBaseSqlQuery,\n}: QueryProviderProps) {\n const contextValue = useMemo<QueryContextType>(\n () => ({\n sql,\n isExecuting,\n error,\n baseResult,\n currentResult,\n onSqlChange,\n onExecute,\n onCancel,\n // OSS fields\n sqlQuery,\n setSqlQuery,\n primaryKeys,\n setPrimaryKeys,\n isCustomQueries,\n setCustomQueries,\n baseSqlQuery,\n setBaseSqlQuery,\n }),\n [\n sql,\n isExecuting,\n error,\n baseResult,\n currentResult,\n onSqlChange,\n onExecute,\n onCancel,\n sqlQuery,\n setSqlQuery,\n primaryKeys,\n setPrimaryKeys,\n isCustomQueries,\n setCustomQueries,\n baseSqlQuery,\n setBaseSqlQuery,\n ],\n );\n\n return (\n <QueryContext.Provider value={contextValue}>\n {children}\n </QueryContext.Provider>\n );\n}\n\nexport function useQueryContext(): QueryContextType {\n return useContext(QueryContext);\n}\n","\"use client\";\n\nimport { type ReactNode, useState } from \"react\";\nimport { CheckProvider, useCheckContext } from \"../providers\";\n\ninterface CheckContextAdapterProps {\n children: ReactNode;\n}\n\n/**\n * OSS-compatible CheckContext type with required fields.\n * The @datarecce/ui CheckContextType has optional OSS fields,\n * but OSS components expect them to be defined.\n */\nexport interface OSSCheckContext {\n latestSelectedCheckId: string;\n setLatestSelectedCheckId: (checkId: string) => void;\n}\n\n/**\n * CheckContextAdapter bridges OSS with @datarecce/ui's CheckProvider.\n *\n * The OSS RecceCheckContext was very simple - just selection state:\n * - latestSelectedCheckId: string\n * - setLatestSelectedCheckId: (checkId: string) => void\n *\n * This adapter manages internal state and provides the OSS interface\n * through the @datarecce/ui CheckProvider.\n */\nexport function CheckContextAdapter({ children }: CheckContextAdapterProps) {\n const [selectedCheckId, setSelectedCheckId] = useState<string>(\"\");\n\n return (\n <CheckProvider\n selectedCheckId={selectedCheckId}\n onSelectCheck={setSelectedCheckId}\n latestSelectedCheckId={selectedCheckId}\n setLatestSelectedCheckId={setSelectedCheckId}\n >\n {children}\n </CheckProvider>\n );\n}\n\n// Note: Check, CheckContextType, CheckProviderProps are now imported directly from @datarecce/ui/providers\n// This adapter only exports CheckContextAdapter, useRecceCheckContext, and OSSCheckContext type\n\n// No-op fallback for when hook is used outside provider\nconst noopSetCheckId = () => {\n // Intentionally empty - fallback when used outside CheckContextAdapter\n};\n\n/**\n * OSS-compatible hook that returns the check context with guaranteed non-optional fields.\n * This wraps @datarecce/ui's useCheckContext and provides type safety for OSS components.\n */\nexport function useRecceCheckContext(): OSSCheckContext {\n const ctx = useCheckContext();\n\n // Return OSS-compatible interface with guaranteed values\n // The CheckContextAdapter ensures these are always set\n return {\n latestSelectedCheckId: ctx.latestSelectedCheckId ?? \"\",\n setLatestSelectedCheckId: ctx.setLatestSelectedCheckId ?? noopSetCheckId,\n };\n}\n\n// Note: useCheckContext is now imported directly from @datarecce/ui/providers\n","export type TypeCategory =\n | \"integer\"\n | \"number\"\n | \"text\"\n | \"boolean\"\n | \"date\"\n | \"datetime\"\n | \"time\"\n | \"binary\"\n | \"json\"\n | \"array\"\n | \"geography\"\n | \"unknown\";\n\nconst CATEGORY_MAP: Record<string, TypeCategory> = {\n // integer\n INTEGER: \"integer\",\n INT: \"integer\",\n BIGINT: \"integer\",\n SMALLINT: \"integer\",\n TINYINT: \"integer\",\n INT64: \"integer\",\n INT32: \"integer\",\n INT16: \"integer\",\n INT8: \"integer\",\n INT4: \"integer\",\n INT2: \"integer\",\n MEDIUMINT: \"integer\",\n SERIAL: \"integer\",\n BIGSERIAL: \"integer\",\n SMALLSERIAL: \"integer\",\n\n // number\n DOUBLE: \"number\",\n FLOAT: \"number\",\n REAL: \"number\",\n NUMERIC: \"number\",\n DECIMAL: \"number\",\n NUMBER: \"number\",\n FLOAT64: \"number\",\n FLOAT32: \"number\",\n \"DOUBLE PRECISION\": \"number\",\n\n // text\n VARCHAR: \"text\",\n TEXT: \"text\",\n STRING: \"text\",\n CHAR: \"text\",\n \"CHARACTER VARYING\": \"text\",\n CHARACTER: \"text\",\n NCHAR: \"text\",\n NVARCHAR: \"text\",\n VARCHAR2: \"text\",\n NVARCHAR2: \"text\",\n CLOB: \"text\",\n NCLOB: \"text\",\n TINYTEXT: \"text\",\n MEDIUMTEXT: \"text\",\n LONGTEXT: \"text\",\n\n // boolean\n BOOLEAN: \"boolean\",\n BOOL: \"boolean\",\n\n // date\n DATE: \"date\",\n\n // datetime\n TIMESTAMP: \"datetime\",\n DATETIME: \"datetime\",\n TIMESTAMP_NTZ: \"datetime\",\n TIMESTAMP_LTZ: \"datetime\",\n TIMESTAMP_TZ: \"datetime\",\n TIMESTAMPTZ: \"datetime\",\n \"TIMESTAMP WITH TIME ZONE\": \"datetime\",\n \"TIMESTAMP WITHOUT TIME ZONE\": \"datetime\",\n \"TIMESTAMP WITH LOCAL TIME ZONE\": \"datetime\",\n DATETIME2: \"datetime\",\n SMALLDATETIME: \"datetime\",\n DATETIMEOFFSET: \"datetime\",\n\n // time\n TIME: \"time\",\n TIMETZ: \"time\",\n \"TIME WITH TIME ZONE\": \"time\",\n \"TIME WITHOUT TIME ZONE\": \"time\",\n\n // binary\n BINARY: \"binary\",\n VARBINARY: \"binary\",\n BYTES: \"binary\",\n BLOB: \"binary\",\n BYTEA: \"binary\",\n TINYBLOB: \"binary\",\n MEDIUMBLOB: \"binary\",\n LONGBLOB: \"binary\",\n\n // json\n JSON: \"json\",\n JSONB: \"json\",\n VARIANT: \"json\",\n OBJECT: \"json\",\n STRUCT: \"json\",\n MAP: \"json\",\n\n // array\n ARRAY: \"array\",\n LIST: \"array\",\n\n // geography\n GEOGRAPHY: \"geography\",\n GEOMETRY: \"geography\",\n POINT: \"geography\",\n LINESTRING: \"geography\",\n POLYGON: \"geography\",\n MULTIPOINT: \"geography\",\n MULTILINESTRING: \"geography\",\n MULTIPOLYGON: \"geography\",\n GEOMETRYCOLLECTION: \"geography\",\n SDO_GEOMETRY: \"geography\",\n};\n\n/**\n * Classifies a raw database type string into a TypeCategory.\n *\n * - Case-insensitive\n * - Strips parenthesized parameters before matching (e.g. VARCHAR(256) -> VARCHAR)\n * - Special case: TINYINT(1) maps to \"boolean\", plain TINYINT maps to \"integer\"\n */\nexport function classifyType(rawType: string): TypeCategory {\n const trimmed = rawType.trim().toUpperCase();\n\n if (!trimmed) {\n return \"unknown\";\n }\n\n // Special case: TINYINT(1) is boolean\n if (/^TINYINT\\s*\\(\\s*1\\s*\\)$/.test(trimmed)) {\n return \"boolean\";\n }\n\n // Strip parenthesized parameters (indexOf avoids regex backtracking)\n const parenIdx = trimmed.indexOf(\"(\");\n const base = parenIdx === -1 ? trimmed : trimmed.slice(0, parenIdx).trimEnd();\n\n return CATEGORY_MAP[base] ?? \"unknown\";\n}\n","import type { CSSProperties } from \"react\";\nimport { useId } from \"react\";\n\ninterface IconProps {\n size?: number;\n style?: CSSProperties;\n className?: string;\n}\n\n/**\n * All icons share a uniform viewBox (30x18) with a rounded-rect border.\n * Content (text or line-art) is drawn inside the box.\n * The box stroke and content fill/stroke all use currentColor.\n */\nconst VB_W = 30;\nconst VB_H = 18;\nconst BOX_RX = 3;\nconst BOX_STROKE = 1.2;\nconst BOX_INSET = 0.6; // half stroke so border sits inside viewBox\n\nfunction BoxedSvg({\n size = 24,\n style,\n className,\n children,\n}: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n viewBox={`0 0 ${VB_W} ${VB_H}`}\n width={size}\n height={(size * VB_H) / VB_W}\n style={style}\n className={className}\n aria-hidden=\"true\"\n >\n <rect\n x={BOX_INSET}\n y={BOX_INSET}\n width={VB_W - BOX_INSET * 2}\n height={VB_H - BOX_INSET * 2}\n rx={BOX_RX}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={BOX_STROKE}\n />\n {children}\n </svg>\n );\n}\n\nfunction TextIcon({\n text,\n size,\n style,\n className,\n}: IconProps & { text: string }) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <text\n x={VB_W / 2}\n y={VB_H / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={10.5}\n fontFamily=\"monospace\"\n fontWeight={500}\n fill=\"currentColor\"\n >\n {text}\n </text>\n </BoxedSvg>\n );\n}\n\nexport function IntegerIcon(props: IconProps) {\n return <TextIcon text=\"123\" {...props} />;\n}\n\nexport function NumberIcon(props: IconProps) {\n return <TextIcon text=\"1.2\" {...props} />;\n}\n\nexport function TextTypeIcon(props: IconProps) {\n return <TextIcon text=\"abc\" {...props} />;\n}\n\nexport function BooleanIcon(props: IconProps) {\n return <TextIcon text=\"T/F\" {...props} />;\n}\n\n/**\n * Two-tone split icon — left half filled with \"0\", right half with \"1\"\n */\nexport function BinaryIcon({ size, style, className }: IconProps) {\n const maskId = useId();\n const x = BOX_INSET;\n const y = BOX_INSET;\n const h = VB_H - BOX_INSET * 2;\n const mid = VB_W / 2;\n\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <mask id={maskId}>\n <rect x={x} y={y} width={mid - x} height={h} fill=\"white\" />\n <text\n x={mid / 2}\n y={VB_H / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={10}\n fontFamily=\"monospace\"\n fontWeight={700}\n fill=\"black\"\n >\n 0\n </text>\n </mask>\n <path\n d={`M${mid} ${y} H${x + BOX_RX} Q${x} ${y} ${x} ${y + BOX_RX} V${y + h - BOX_RX} Q${x} ${y + h} ${x + BOX_RX} ${y + h} H${mid} Z`}\n fill=\"currentColor\"\n mask={`url(#${maskId})`}\n />\n <text\n x={mid + (VB_W - mid) / 2}\n y={VB_H / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={10}\n fontFamily=\"monospace\"\n fontWeight={700}\n fill=\"currentColor\"\n >\n 1\n </text>\n </BoxedSvg>\n );\n}\n\nexport function JsonIcon(props: IconProps) {\n return <TextIcon text=\"{ }\" {...props} />;\n}\n\n/**\n * Square brackets with \"1,2\" text — for ARRAY/LIST types\n */\nexport function ArrayIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1.2}\n strokeLinecap=\"round\"\n >\n {/* Left bracket */}\n <line x1={6} y1={5} x2={4} y2={5} />\n <line x1={4} y1={5} x2={4} y2={13} />\n <line x1={4} y1={13} x2={6} y2={13} />\n {/* Right bracket */}\n <line x1={24} y1={5} x2={26} y2={5} />\n <line x1={26} y1={5} x2={26} y2={13} />\n <line x1={26} y1={13} x2={24} y2={13} />\n </g>\n <text\n x={VB_W / 2}\n y={VB_H / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={9}\n fontFamily=\"monospace\"\n fontWeight={500}\n fill=\"currentColor\"\n >\n 1,2\n </text>\n </BoxedSvg>\n );\n}\n\nexport function UnknownIcon(props: IconProps) {\n return <TextIcon text=\"···\" {...props} />;\n}\n\n/**\n * Calendar icon inside box — for DATE type\n */\nexport function DateIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n transform=\"translate(9, 2.5)\"\n >\n {/* Calendar body */}\n <rect x={0} y={2} width={11} height={9.5} rx={1.2} />\n {/* Top hooks */}\n <line x1={3} y1={0.5} x2={3} y2={3.5} />\n <line x1={8} y1={0.5} x2={8} y2={3.5} />\n {/* Divider */}\n <line x1={0} y1={5.5} x2={11} y2={5.5} />\n </g>\n </BoxedSvg>\n );\n}\n\n/**\n * Calendar + clock icon inside box — for DATETIME/TIMESTAMP types\n */\nexport function DatetimeIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n transform=\"translate(4, 2.5)\"\n >\n {/* Calendar (smaller, left) */}\n <rect x={0} y={2} width={9.5} height={8.5} rx={1.2} />\n <line x1={2.5} y1={0.5} x2={2.5} y2={3.5} />\n <line x1={7} y1={0.5} x2={7} y2={3.5} />\n <line x1={0} y1={5} x2={9.5} y2={5} />\n {/* Clock (small, right) */}\n <circle cx={17} cy={8.5} r={3.2} />\n <line x1={17} y1={6.8} x2={17} y2={8.5} />\n <line x1={17} y1={8.5} x2={18.3} y2={9.5} />\n </g>\n </BoxedSvg>\n );\n}\n\n/**\n * Clock icon inside box — for TIME type\n */\nexport function TimeIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n transform=\"translate(10, 3.5)\"\n >\n <circle cx={5} cy={5.5} r={5} />\n <line x1={5} y1={3} x2={5} y2={5.5} />\n <line x1={5} y1={5.5} x2={7} y2={6.8} />\n </g>\n </BoxedSvg>\n );\n}\n\n/**\n * Map pin (teardrop) icon inside box — for GEOGRAPHY/GEOMETRY types\n */\nexport function GeographyIcon({ size, style, className }: IconProps) {\n return (\n <BoxedSvg size={size} style={style} className={className}>\n <g\n stroke=\"currentColor\"\n fill=\"none\"\n strokeWidth={1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path\n d=\"M15 14.5 C15 14.5 10.5 11 10.5 8 A4.5 4.5 0 0 1 19.5 8 C19.5 11 15 14.5 15 14.5 Z\"\n strokeWidth={1.2}\n />\n <circle cx={15} cy={8} r={1.5} fill=\"currentColor\" stroke=\"none\" />\n </g>\n </BoxedSvg>\n );\n}\n","export interface ColumnTooltipInput {\n name: string;\n status?:\n | \"added\"\n | \"removed\"\n | \"type_changed\"\n | \"definition_changed\"\n | \"unchanged\";\n baseType?: string;\n currentType?: string;\n cllAvailable?: boolean;\n}\n\nexport function buildColumnTooltip(input: ColumnTooltipInput): string {\n const { name, status, baseType, currentType, cllAvailable } = input;\n\n let text: string;\n\n switch (status) {\n case \"added\":\n text = currentType ? `${name} added ${currentType}` : `${name} added`;\n break;\n\n case \"removed\":\n // Removed columns never get the CLL suffix\n return `deleted ${name}`;\n\n case \"type_changed\":\n text = `${name}, was ${baseType} now ${currentType}`;\n break;\n\n case \"definition_changed\":\n text = currentType\n ? `${name} ${currentType} changed definition`\n : `${name} changed definition`;\n break;\n\n case \"unchanged\":\n text = currentType ? `${name} ${currentType}` : name;\n break;\n\n default: {\n // No status provided — infer from types\n if (baseType && currentType && baseType !== currentType) {\n text = `${name}, was ${baseType} now ${currentType}`;\n } else if (currentType) {\n text = `${name} ${currentType}`;\n } else {\n text = name;\n }\n break;\n }\n }\n\n if (cllAvailable) {\n text += \" \\u00b7 Click for column lineage\";\n }\n\n return text;\n}\n","import Tooltip from \"@mui/material/Tooltip\";\nimport type { CSSProperties } from \"react\";\nimport type { TypeCategory } from \"./classifyType\";\nimport { classifyType } from \"./classifyType\";\nimport {\n ArrayIcon,\n BinaryIcon,\n BooleanIcon,\n DateIcon,\n DatetimeIcon,\n GeographyIcon,\n IntegerIcon,\n JsonIcon,\n NumberIcon,\n TextTypeIcon,\n TimeIcon,\n UnknownIcon,\n} from \"./icons\";\n\nexport { classifyType };\nexport type { TypeCategory };\nexport type { ColumnTooltipInput } from \"./tooltipText\";\nexport { buildColumnTooltip } from \"./tooltipText\";\n\nconst ICON_MAP: Record<\n TypeCategory,\n React.ComponentType<{\n size?: number;\n style?: CSSProperties;\n className?: string;\n }>\n> = {\n integer: IntegerIcon,\n number: NumberIcon,\n text: TextTypeIcon,\n boolean: BooleanIcon,\n date: DateIcon,\n datetime: DatetimeIcon,\n time: TimeIcon,\n binary: BinaryIcon,\n json: JsonIcon,\n array: ArrayIcon,\n geography: GeographyIcon,\n unknown: UnknownIcon,\n};\n\nexport interface DataTypeIconProps {\n type: string;\n size?: number;\n style?: CSSProperties;\n className?: string;\n disableTooltip?: boolean;\n}\n\nexport function DataTypeIcon({\n type,\n size = 24,\n style,\n className,\n disableTooltip,\n}: DataTypeIconProps) {\n const category = classifyType(type);\n const IconComponent = ICON_MAP[category];\n\n const icon = (\n <span\n data-testid=\"data-type-icon\"\n style={{ display: \"inline-flex\", alignItems: \"center\", lineHeight: 0 }}\n >\n <IconComponent size={size} style={style} className={className} />\n </span>\n );\n\n if (disableTooltip) {\n return icon;\n }\n\n return (\n <Tooltip title={type} placement=\"top\" arrow>\n {icon}\n </Tooltip>\n );\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Chip from \"@mui/material/Chip\";\nimport { Handle, Position } from \"@xyflow/react\";\nimport type { MouseEvent } from \"react\";\nimport { memo, useState } from \"react\";\nimport { DataTypeIcon } from \"../../ui/DataTypeIcon\";\n\n/**\n * Transformation type for column-level lineage\n */\nexport type ColumnTransformationType =\n | \"passthrough\"\n | \"renamed\"\n | \"derived\"\n | \"source\"\n | \"unknown\";\n\n/**\n * Column change status for diff views\n */\nexport type ColumnChangeStatus = \"added\" | \"removed\" | \"modified\";\n\n/**\n * Data structure for a column node\n */\nexport interface LineageColumnNodeData extends Record<string, unknown> {\n /** Column name */\n column: string;\n /** Column data type (e.g., \"VARCHAR\", \"INTEGER\") */\n type?: string;\n /** ID of the parent model/table node */\n nodeId: string;\n /** Transformation type for this column */\n transformationType?: ColumnTransformationType;\n /** Change status for diff views */\n changeStatus?: ColumnChangeStatus;\n /** Whether the column is highlighted */\n isHighlighted?: boolean;\n /** Whether the column is selected/focused */\n isFocused?: boolean;\n}\n\n/**\n * Props for the LineageColumnNode component\n */\nexport interface LineageColumnNodeProps {\n /** Unique node ID */\n id: string;\n /** Node data */\n data: LineageColumnNodeData;\n /** Whether the node is selected */\n selected?: boolean;\n\n // === New props for OSS feature parity ===\n\n /**\n * Whether to show content (used for zoom-level visibility)\n * When false, the node renders nothing (hidden at low zoom levels)\n * @default true\n */\n showContent?: boolean;\n\n /**\n * Whether to show change analysis mode\n * When true and changeStatus exists, shows change status indicator\n * When false, shows transformation type indicator\n * @default false\n */\n showChangeAnalysis?: boolean;\n\n /**\n * Whether to use dark mode styling\n * @default false\n */\n isDark?: boolean;\n\n // === Callbacks ===\n\n /** Callback when column is clicked */\n onColumnClick?: (columnId: string) => void;\n\n /**\n * Callback when context menu is requested (kebab menu click)\n * When provided, shows kebab menu on hover\n */\n onContextMenu?: (event: MouseEvent, columnId: string) => void;\n}\n\n/**\n * Default column height in pixels\n */\nexport const COLUMN_NODE_HEIGHT = 24;\n\n/**\n * Default column width in pixels\n */\nexport const COLUMN_NODE_WIDTH = 280;\n\n/**\n * Colors for change status indicators\n */\nconst changeStatusColors: Record<ColumnChangeStatus, string> = {\n added: \"#22c55e\",\n removed: \"#ef4444\",\n modified: \"#f59e0b\",\n};\n\n/**\n * Colors for transformation type chips\n */\nconst transformationColors: Record<\n ColumnTransformationType,\n { letter: string; color: \"default\" | \"warning\" | \"info\" | \"error\" }\n> = {\n passthrough: { letter: \"P\", color: \"default\" },\n renamed: { letter: \"R\", color: \"warning\" },\n derived: { letter: \"D\", color: \"warning\" },\n source: { letter: \"S\", color: \"info\" },\n unknown: { letter: \"U\", color: \"error\" },\n};\n\n/**\n * KebabMenuIcon - Inline SVG to avoid react-icons dependency\n */\nfunction KebabMenuIcon({ size = 14 }: { size?: number }) {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 16 16\"\n fill=\"currentColor\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle cx=\"8\" cy=\"3\" r=\"1.5\" />\n <circle cx=\"8\" cy=\"8\" r=\"1.5\" />\n <circle cx=\"8\" cy=\"13\" r=\"1.5\" />\n </svg>\n );\n}\n\n/**\n * ChangeStatusIndicator - Shows change status icon\n */\nfunction ChangeStatusIndicator({\n changeStatus,\n}: {\n changeStatus?: ColumnChangeStatus;\n}) {\n if (!changeStatus) {\n return null;\n }\n\n const color = changeStatusColors[changeStatus];\n const symbols: Record<ColumnChangeStatus, string> = {\n added: \"+\",\n removed: \"-\",\n modified: \"~\",\n };\n\n return (\n <Box\n sx={{\n width: 14,\n height: 14,\n borderRadius: \"50%\",\n backgroundColor: color,\n color: \"white\",\n fontSize: 10,\n fontWeight: \"bold\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flexShrink: 0,\n }}\n >\n {symbols[changeStatus]}\n </Box>\n );\n}\n\n/**\n * TransformationIndicator - Shows transformation type chip\n */\nfunction TransformationIndicator({\n transformationType,\n}: {\n transformationType?: ColumnTransformationType;\n}) {\n if (!transformationType) {\n return null;\n }\n\n const config = transformationColors[transformationType];\n\n return (\n <Chip\n label={config.letter}\n size=\"small\"\n color={config.color}\n sx={{\n fontSize: \"8pt\",\n height: 18,\n minWidth: 18,\n \"& .MuiChip-label\": {\n px: 0.5,\n },\n }}\n />\n );\n}\n\n/**\n * LineageColumnNode Component\n *\n * A pure presentation component for rendering individual columns\n * in column-level lineage visualizations using React Flow.\n *\n * @example Basic usage\n * ```tsx\n * import { LineageColumnNode } from '@datarecce/ui/primitives';\n *\n * // Register as a React Flow node type\n * const nodeTypes = {\n * columnNode: LineageColumnNode,\n * };\n *\n * function ColumnLineageGraph() {\n * return (\n * <ReactFlow nodes={columnNodes} edges={edges} nodeTypes={nodeTypes} />\n * );\n * }\n * ```\n *\n * @example Node data structure\n * ```tsx\n * const columnNode = {\n * id: 'users-id',\n * type: 'columnNode',\n * data: {\n * column: 'id',\n * type: 'INTEGER',\n * nodeId: 'users',\n * transformationType: 'passthrough',\n * changeStatus: undefined,\n * isHighlighted: true,\n * },\n * position: { x: 0, y: 0 },\n * };\n * ```\n *\n * @example With change analysis mode\n * ```tsx\n * // In change analysis mode, shows change status instead of transformation type\n * <LineageColumnNode\n * showChangeAnalysis={true}\n * showContent={zoomLevel > 0.3}\n * onContextMenu={(e, columnId) => showMenu(e, columnId)}\n * />\n * ```\n */\nfunction LineageColumnNodeComponent({\n id,\n data,\n showContent = true,\n showChangeAnalysis = false,\n isDark = false,\n onColumnClick,\n onContextMenu,\n}: LineageColumnNodeProps) {\n const {\n column,\n type,\n transformationType,\n changeStatus,\n isHighlighted = true,\n isFocused = false,\n } = data;\n\n const [isHovered, setIsHovered] = useState(false);\n\n // Hide node when showContent is false (low zoom level)\n if (!showContent) {\n return null;\n }\n\n // Determine what indicator to show based on showChangeAnalysis mode\n const shouldShowChangeStatus = showChangeAnalysis && changeStatus;\n\n return (\n <Box\n onClick={() => onColumnClick?.(id)}\n sx={{\n display: \"flex\",\n width: COLUMN_NODE_WIDTH,\n padding: \"0px 10px\",\n border: \"1px solid\",\n borderColor: \"divider\",\n backgroundColor: isFocused\n ? \"action.selected\"\n : isHovered\n ? \"action.hover\"\n : \"background.paper\",\n filter: isHighlighted ? \"none\" : \"opacity(0.2) grayscale(50%)\",\n cursor: \"pointer\",\n transition: \"background-color 0.15s ease\",\n }}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <Box\n sx={{\n display: \"flex\",\n fontSize: \"11px\",\n color: \"text.primary\",\n width: \"100%\",\n gap: \"6px\",\n alignItems: \"center\",\n height: `${COLUMN_NODE_HEIGHT - 1}px`,\n }}\n >\n {/* Status indicator - based on showChangeAnalysis mode */}\n {shouldShowChangeStatus ? (\n <ChangeStatusIndicator changeStatus={changeStatus} />\n ) : (\n <TransformationIndicator transformationType={transformationType} />\n )}\n\n {/* Column name */}\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n flexGrow: 1,\n height: `${COLUMN_NODE_HEIGHT + 1}px`,\n lineHeight: `${COLUMN_NODE_HEIGHT + 1}px`,\n }}\n >\n {column}\n </Box>\n\n {/* Column type or kebab menu */}\n {isHovered && onContextMenu ? (\n <Box\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n cursor: \"pointer\",\n \"&:hover\": { color: \"text.primary\" },\n }}\n onClick={(e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onContextMenu(e, id);\n }}\n data-testid=\"column-kebab-menu\"\n >\n <KebabMenuIcon size={14} />\n </Box>\n ) : (\n type && (\n <DataTypeIcon\n type={type}\n size={16}\n style={{ flexShrink: 0, opacity: 0.7 }}\n />\n )\n )}\n </Box>\n\n {/* Connection handles */}\n <Handle\n type=\"target\"\n position={Position.Left}\n isConnectable={false}\n style={{\n left: 0,\n visibility: \"hidden\",\n }}\n />\n <Handle\n type=\"source\"\n position={Position.Right}\n isConnectable={false}\n style={{\n right: 0,\n visibility: \"hidden\",\n }}\n />\n </Box>\n );\n}\n\nexport const LineageColumnNode = memo(LineageColumnNodeComponent);\nLineageColumnNode.displayName = \"LineageColumnNode\";\n","\"use client\";\n\n/**\n * @file styles.ts\n * @description Styling utilities for lineage components\n *\n * Provides icons, colors, and styling helpers for:\n * - Change status visualization (added, removed, modified)\n * - Resource type icons (model, source, seed, etc.)\n *\n * Source: Ported from OSS js/src/components/lineage/styles.tsx\n */\n\nimport type { ComponentType, SVGProps } from \"react\";\nimport { colors } from \"../../theme/colors\";\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\n/**\n * Change status for diff visualization\n */\nexport type ChangeStatus = \"added\" | \"removed\" | \"modified\" | \"unchanged\";\n\n/**\n * Resource types supported by dbt/lineage\n */\nexport type ResourceType =\n | \"model\"\n | \"source\"\n | \"seed\"\n | \"snapshot\"\n | \"metric\"\n | \"exposure\"\n | \"semantic_model\";\n\n/**\n * Icon component type\n */\nexport type IconComponent = ComponentType<SVGProps<SVGSVGElement>>;\n\n/**\n * Result from getIconForChangeStatus\n */\nexport interface ChangeStatusStyle {\n /** CSS color value for the status */\n color: string;\n /** Hex color value (same as color for compatibility) */\n hexColor: string;\n /** Background color for light/dark mode */\n backgroundColor: string;\n /** Hex background color (same as backgroundColor for compatibility) */\n hexBackgroundColor: string;\n /** Icon component for the status, undefined if no change */\n icon: IconComponent | undefined;\n}\n\n/**\n * Result from getIconForResourceType\n */\nexport interface ResourceTypeStyle {\n /** CSS color value for the resource type */\n color: string;\n /** Icon component for the resource type, undefined if unknown */\n icon: IconComponent | undefined;\n}\n\n// =============================================================================\n// SVG ICON COMPONENTS\n// =============================================================================\n\n/**\n * Plus icon for \"added\" status\n * Based on VSCode's VscDiffAdded\n */\nexport const IconAdded: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 2v12h12V2H2zm6.5 2.5v2.5h2.5v1H8.5v2.5h-1V8H5V7h2.5V4.5h1z\"\n />\n </svg>\n);\n\n/**\n * Minus icon for \"removed\" status\n * Based on VSCode's VscDiffRemoved\n */\nexport const IconRemoved: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 2v12h12V2H2zm3 5.5h6v1H5v-1z\"\n />\n </svg>\n);\n\n/**\n * Tilde icon for \"modified\" status\n * Based on VSCode's VscDiffModified\n */\nexport const IconModified: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 2v12h12V2H2z\" />\n <path d=\"M10.6 8.7c-.1.1-.3.2-.5.2h-.1l-1.4-.3c-.5-.1-1.1-.3-1.8-.3-.6 0-1 .2-1.3.5-.2.2-.2.4-.2.7v.1l-.9.3v-.2c0-.3 0-.6.1-.9.2-.4.5-.8 1-1.1.5-.4 1.2-.5 2-.5h.2l1.5.4h.1c.5.1 1 .3 1.5.3.6 0 .9-.2 1.2-.4.2-.2.3-.5.3-.8v-.3l.8-.3h.1v.3c0 .6-.2 1.2-.6 1.6-.2.2-.3.4-.5.5l-.5.2z\" />\n </svg>\n);\n\n/**\n * Dot icon for \"changed\" status (generic change indicator)\n */\nexport const IconChanged: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z\"\n />\n </svg>\n);\n\n/**\n * Icon for modified with downstream impact indicator\n */\nexport const IconModifiedDownstream: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 2v4h-1v4h1v4h4v1h4v-1h4v-4h1v-4h-1v-4h-4v-1h-4v1h-4z\"\n />\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z\"\n />\n </svg>\n);\n\n// =============================================================================\n// RESOURCE TYPE ICONS (inline SVGs to avoid react-icons dependency)\n// =============================================================================\n\n/**\n * Cube icon for \"model\" resource type\n */\nexport const IconModel: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M239.1 6.3l-208 78c-18.7 7-31.1 25-31.1 45v225.1c0 18.2 10.3 34.8 26.5 42.9l208 104c13.5 6.8 29.4 6.8 42.9 0l208-104c16.3-8.1 26.5-24.8 26.5-42.9V129.3c0-20-12.4-37.9-31.1-44.9l-208-78C262 2.2 250 2.2 239.1 6.3zM256 68.4l192 72v1.1l-192 78-192-78v-1.1l192-72zm32 356V275.5l160-65v133.9l-160 80z\" />\n </svg>\n);\n\n/**\n * Database icon for \"source\" resource type\n */\nexport const IconSource: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 448 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M448 73.143v45.714C448 159.143 347.667 192 224 192S0 159.143 0 118.857V73.143C0 32.857 100.333 0 224 0s224 32.857 224 73.143zM448 176v102.857C448 319.143 347.667 352 224 352S0 319.143 0 278.857V176c48.125 33.143 136.208 48.572 224 48.572S399.874 209.143 448 176zm0 160v102.857C448 479.143 347.667 512 224 512S0 479.143 0 438.857V336c48.125 33.143 136.208 48.572 224 48.572S399.874 369.143 448 336z\" />\n </svg>\n);\n\n/**\n * Seedling icon for \"seed\" resource type\n */\nexport const IconSeed: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M64 96H0c0 123.7 100.3 224 224 224v144c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V320C288 196.3 187.7 96 64 96zm384-64c-84.2 0-157.4 46.5-195.7 115.2 27.7 30.2 48.2 66.9 59 107.6C424 243.1 512 147.9 512 32h-64z\" />\n </svg>\n);\n\n/**\n * Camera icon for \"snapshot\" resource type\n */\nexport const IconSnapshot: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M512 144v288c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48h88l12.3-32.9c7-18.7 24.9-31.1 44.9-31.1h125.5c20 0 37.9 12.4 44.9 31.1L376 96h88c26.5 0 48 21.5 48 48zM376 288c0-66.2-53.8-120-120-120s-120 53.8-120 120 53.8 120 120 120 120-53.8 120-120zm-32 0c0 48.5-39.5 88-88 88s-88-39.5-88-88 39.5-88 88-88 88 39.5 88 88z\" />\n </svg>\n);\n\n/**\n * Chart icon for \"metric\" resource type\n */\nexport const IconMetric: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 448 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M160 80c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48h-32c-26.5 0-48-21.5-48-48V80zM0 272c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48v160c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V272zM368 96h32c26.5 0 48 21.5 48 48v288c0 26.5-21.5 48-48 48h-32c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48z\" />\n </svg>\n);\n\n/**\n * Gauge icon for \"exposure\" resource type\n */\nexport const IconExposure: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm320 96c0-15.9-5.8-30.4-15.3-41.6l76.6-147.4c6.1-11.8 1.5-26.3-10.2-32.4s-26.2-1.5-32.4 10.2L262.1 288.3c-2-.2-4-.3-6.1-.3c-35.3 0-64 28.7-64 64s28.7 64 64 64s64-28.7 64-64z\" />\n </svg>\n);\n\n/**\n * Nodes icon for \"semantic_model\" resource type\n */\nexport const IconSemanticModel: IconComponent = (props) => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M418.4 157.9c35.3-8.3 61.6-40 61.6-77.9c0-44.2-35.8-80-80-80c-43.4 0-78.7 34.5-80 77.5L136.2 151.1C121.7 136.8 101.9 128 80 128c-44.2 0-80 35.8-80 80s35.8 80 80 80c12.2 0 23.8-2.7 34.1-7.6L259.7 407.8c-2.4 7.6-3.7 15.8-3.7 24.2c0 44.2 35.8 80 80 80s80-35.8 80-80c0-27.7-14-52.1-35.4-66.4l37.8-207.7zM156.3 232.2c2.2-6.9 3.5-14.2 3.7-21.7l183.8-73.5c3.6 3.5 7.4 6.7 11.6 9.5L317.6 354.1c-5.5 1.3-10.8 3.1-15.8 5.5L156.3 232.2z\" />\n </svg>\n);\n\n// =============================================================================\n// STYLING FUNCTIONS\n// =============================================================================\n\n/**\n * Get icon and colors for a change status\n *\n * @param changeStatus - The change status (added, removed, modified)\n * @param isDark - Whether dark mode is active\n * @returns Object containing color values and icon component\n *\n * @example\n * ```tsx\n * const { color, icon: Icon } = getIconForChangeStatus(\"added\");\n * return <Icon style={{ color }} />;\n * ```\n */\nexport function getIconForChangeStatus(\n changeStatus?: ChangeStatus,\n isDark?: boolean,\n): ChangeStatusStyle {\n if (changeStatus === \"added\") {\n return {\n color: colors.green[500],\n hexColor: colors.green[500],\n backgroundColor: isDark ? colors.green[900] : colors.green[100],\n hexBackgroundColor: isDark ? colors.green[900] : colors.green[100],\n icon: IconAdded,\n };\n }\n\n if (changeStatus === \"removed\") {\n return {\n color: colors.red[500],\n hexColor: colors.red[500],\n backgroundColor: isDark ? colors.red[950] : colors.red[200],\n hexBackgroundColor: isDark ? colors.red[950] : colors.red[200],\n icon: IconRemoved,\n };\n }\n\n if (changeStatus === \"modified\") {\n return {\n color: colors.amber[500],\n hexColor: colors.amber[500],\n backgroundColor: isDark ? colors.amber[900] : colors.amber[100],\n hexBackgroundColor: isDark ? colors.amber[900] : colors.amber[100],\n icon: IconModified,\n };\n }\n\n // Default: no change\n return {\n color: colors.neutral[500],\n hexColor: colors.neutral[500],\n backgroundColor: isDark ? colors.neutral[700] : colors.white,\n hexBackgroundColor: isDark ? colors.neutral[700] : colors.white,\n icon: undefined,\n };\n}\n\n/**\n * Get icon and color for a resource type\n *\n * @param resourceType - The resource type (model, source, seed, etc.)\n * @returns Object containing color and icon component\n *\n * @example\n * ```tsx\n * const { color, icon: Icon } = getIconForResourceType(\"model\");\n * return Icon ? <Icon style={{ color }} /> : null;\n * ```\n */\nexport function getIconForResourceType(\n resourceType?: string,\n): ResourceTypeStyle {\n switch (resourceType) {\n case \"model\":\n return {\n color: colors.cyan[200],\n icon: IconModel,\n };\n case \"source\":\n return {\n color: colors.green[300],\n icon: IconSource,\n };\n case \"seed\":\n return {\n color: colors.green[500],\n icon: IconSeed,\n };\n case \"snapshot\":\n return {\n color: colors.green[500],\n icon: IconSnapshot,\n };\n case \"metric\":\n return {\n color: colors.rose[200],\n icon: IconMetric,\n };\n case \"exposure\":\n return {\n color: colors.rose[200],\n icon: IconExposure,\n };\n case \"semantic_model\":\n return {\n color: colors.rose[400],\n icon: IconSemanticModel,\n };\n default:\n return {\n color: \"inherit\",\n icon: undefined,\n };\n }\n}\n\n// =============================================================================\n// STYLE CONSTANTS\n// =============================================================================\n\n/**\n * Pre-defined colors for change status (for direct usage without function call)\n */\nexport const changeStatusColors: Record<ChangeStatus | \"unchanged\", string> = {\n added: colors.green[500],\n removed: colors.red[500],\n modified: colors.amber[500],\n unchanged: colors.neutral[500],\n};\n\n/**\n * Pre-defined background colors for change status (light mode)\n */\nexport const changeStatusBackgroundsLight: Record<\n ChangeStatus | \"unchanged\",\n string\n> = {\n added: colors.green[100],\n removed: colors.red[200],\n modified: colors.amber[100],\n unchanged: colors.white,\n};\n\n/**\n * Pre-defined background colors for change status (dark mode)\n */\nexport const changeStatusBackgroundsDark: Record<\n ChangeStatus | \"unchanged\",\n string\n> = {\n added: colors.green[900],\n removed: colors.red[950],\n modified: colors.amber[900],\n unchanged: colors.neutral[700],\n};\n","\"use client\";\n\nimport {\n BaseEdge,\n type Edge,\n EdgeLabelRenderer,\n type EdgeProps,\n getBezierPath,\n} from \"@xyflow/react\";\nimport { memo } from \"react\";\n\nexport type EdgeChangeStatus = \"added\" | \"removed\" | \"modified\" | \"unchanged\";\n\nexport interface LineageEdgeData extends Record<string, unknown> {\n /** Change status for diff visualization */\n changeStatus?: EdgeChangeStatus;\n /** Whether this edge is highlighted */\n isHighlighted?: boolean;\n /** Label to display on edge */\n label?: string;\n}\n\nexport type LineageEdgeType = Edge<LineageEdgeData>;\n\nexport type LineageEdgeProps = EdgeProps<LineageEdgeType>;\n\nconst statusColors: Record<EdgeChangeStatus, string> = {\n added: \"#22c55e\",\n removed: \"#ef4444\",\n modified: \"#f59e0b\",\n unchanged: \"#94a3b8\",\n};\n\nfunction LineageEdgeComponent({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n data,\n selected,\n}: LineageEdgeProps) {\n const changeStatus: EdgeChangeStatus = data?.changeStatus ?? \"unchanged\";\n const isHighlighted = data?.isHighlighted ?? false;\n const label = data?.label;\n\n const [edgePath, labelX, labelY] = getBezierPath({\n sourceX,\n sourceY,\n sourcePosition,\n targetX,\n targetY,\n targetPosition,\n });\n\n const strokeColor = statusColors[changeStatus];\n const strokeWidth = isHighlighted || selected ? 2.5 : 1.5;\n const strokeOpacity = isHighlighted || selected ? 1 : 0.6;\n\n return (\n <>\n <BaseEdge\n id={id}\n path={edgePath}\n style={{\n stroke: strokeColor,\n strokeWidth,\n opacity: strokeOpacity,\n }}\n />\n {label && (\n <EdgeLabelRenderer>\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,\n fontSize: 10,\n fontWeight: 500,\n background: \"white\",\n padding: \"2px 4px\",\n borderRadius: 4,\n pointerEvents: \"all\",\n }}\n >\n {label}\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n}\n\nexport const LineageEdge = memo(LineageEdgeComponent);\nLineageEdge.displayName = \"LineageEdge\";\n","\"use client\";\n\n/**\n * @file ChangedOnlyCheckbox.tsx\n * @description Checkbox component for filtering to show only changed rows\n *\n * Framework-agnostic component that works with both Recce OSS and Cloud.\n */\n\nimport Checkbox from \"@mui/material/Checkbox\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ChangedOnlyCheckboxProps {\n /** Whether the changed-only filter is enabled */\n changedOnly?: boolean;\n /** Callback when the checkbox state changes */\n onChange: () => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * A checkbox component for filtering diff results to show only changed rows.\n *\n * @example\n * ```tsx\n * <ChangedOnlyCheckbox\n * changedOnly={viewOptions.changed_only}\n * onChange={() => setChangedOnly(!viewOptions.changed_only)}\n * />\n * ```\n */\nexport function ChangedOnlyCheckbox({\n changedOnly,\n onChange,\n}: ChangedOnlyCheckboxProps) {\n return (\n <FormControlLabel\n control={\n <Checkbox\n checked={changedOnly ?? false}\n onChange={() => {\n onChange();\n }}\n size=\"small\"\n />\n }\n label=\"Changed only\"\n slotProps={{\n typography: { variant: \"body2\" },\n }}\n />\n );\n}\n","\"use client\";\n\n/**\n * @file ToggleSwitch.tsx\n * @description Toggle switch component for switching between two values\n *\n * Framework-agnostic toggle switch that works with both Recce OSS and Cloud.\n */\n\nimport Button from \"@mui/material/Button\";\nimport ButtonGroup from \"@mui/material/ButtonGroup\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ToggleSwitchProps {\n /** Current toggle state */\n value: boolean;\n /** Callback when toggle state changes */\n onChange: (value: boolean) => void;\n /** Label for the \"on\" state */\n textOn?: string;\n /** Label for the \"off\" state */\n textOff?: string;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * A toggle switch component that allows switching between two values.\n *\n * @example\n * ```tsx\n * <ToggleSwitch\n * value={isEnabled}\n * onChange={setIsEnabled}\n * textOff=\"Inline\"\n * textOn=\"Side by side\"\n * />\n * ```\n */\nexport function ToggleSwitch({\n value,\n onChange,\n textOn,\n textOff,\n}: ToggleSwitchProps) {\n return (\n <ButtonGroup variant=\"outlined\" size=\"xsmall\" sx={{ borderRadius: 1 }}>\n <Button\n onClick={() => {\n onChange(false);\n }}\n sx={{\n color: !value ? \"text.primary\" : \"text.disabled\",\n bgcolor: !value ? \"background.paper\" : \"action.hover\",\n borderColor: \"divider\",\n \"&:hover\": {\n bgcolor: !value ? \"background.paper\" : \"action.selected\",\n borderColor: \"divider\",\n },\n }}\n >\n {textOff ?? \"Off\"}\n </Button>\n <Button\n onClick={() => {\n onChange(true);\n }}\n sx={{\n color: value ? \"text.primary\" : \"text.disabled\",\n bgcolor: value ? \"background.paper\" : \"action.hover\",\n borderColor: \"divider\",\n \"&:hover\": {\n bgcolor: value ? \"background.paper\" : \"action.selected\",\n borderColor: \"divider\",\n },\n }}\n >\n {textOn ?? \"On\"}\n </Button>\n </ButtonGroup>\n );\n}\n","\"use client\";\n\n/**\n * @file DiffDisplayModeSwitch.tsx\n * @description Switch component for toggling between inline and side-by-side diff display modes\n *\n * Framework-agnostic component that works with both Recce OSS and Cloud.\n */\n\nimport { DiffText } from \"./DiffText\";\nimport { ToggleSwitch } from \"./ToggleSwitch\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DiffDisplayMode = \"inline\" | \"side_by_side\";\n\nexport interface DiffDisplayModeSwitchProps {\n /** Current display mode */\n displayMode: DiffDisplayMode;\n /** Callback when display mode changes */\n onDisplayModeChanged: (displayMode: DiffDisplayMode) => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * A switch component for toggling between inline and side-by-side diff display modes.\n *\n * When in inline mode, also shows color legend (Base = red, Current = green).\n *\n * @example\n * ```tsx\n * <DiffDisplayModeSwitch\n * displayMode=\"inline\"\n * onDisplayModeChanged={(mode) => setDisplayMode(mode)}\n * />\n * ```\n */\nexport function DiffDisplayModeSwitch({\n displayMode,\n onDisplayModeChanged,\n}: DiffDisplayModeSwitchProps) {\n return (\n <>\n {displayMode === \"inline\" && (\n <>\n <DiffText\n value=\"Base\"\n colorPalette=\"red\"\n grayOut={false}\n fontSize=\"10pt\"\n noCopy\n />\n <DiffText\n value=\"Current\"\n colorPalette=\"green\"\n grayOut={false}\n fontSize=\"10pt\"\n noCopy\n />\n </>\n )}\n <ToggleSwitch\n value={displayMode === \"side_by_side\"}\n onChange={(value) => {\n onDisplayModeChanged(value ? \"side_by_side\" : \"inline\");\n }}\n textOff=\"Inline\"\n textOn=\"Side by side\"\n />\n </>\n );\n}\n","/**\n * @file DropdownValuesInput.tsx\n * @description Multi-select dropdown input component with filtering and custom value support\n *\n * Features:\n * - Dropdown menu with suggestion list\n * - Filter/search functionality (case-insensitive)\n * - Chip-based value display with removal\n * - Keyboard navigation (Enter, comma, Backspace)\n * - Custom value addition\n * - Configurable size variants\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Chip from \"@mui/material/Chip\";\nimport Divider from \"@mui/material/Divider\";\nimport InputBase from \"@mui/material/InputBase\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport MuiTooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport _ from \"lodash\";\nimport { type MouseEvent, useRef, useState } from \"react\";\n\n/**\n * Size variants for the dropdown input\n */\nexport type DropdownValuesInputSize = \"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\";\n\n/**\n * Props for the DropdownValuesInput component\n */\nexport interface DropdownValuesInputProps {\n /** Unit name for pluralization in \"X {unitName}s selected\" display */\n unitName: string;\n /** List of suggested values to show in dropdown */\n suggestionList?: string[];\n /** Initial selected values */\n defaultValues?: string[];\n /** Callback when selected values change */\n onValuesChange: (values: string[]) => void;\n /** Optional CSS class name */\n className?: string;\n /** Size variant for the input */\n size?: DropdownValuesInputSize;\n /** Width of the input (CSS value or number in pixels) */\n width?: string | number;\n /** Placeholder text when no values are selected */\n placeholder?: string;\n /** Whether the input is disabled */\n disabled?: boolean;\n}\n\n/**\n * A multi-select dropdown input component for selecting multiple values.\n *\n * Provides a dropdown menu with suggestion list, filtering, and the ability\n * to add custom values. Selected values are displayed as chips that can be\n * removed individually.\n *\n * @example\n * ```tsx\n * <DropdownValuesInput\n * unitName=\"key\"\n * suggestionList={[\"id\", \"name\", \"email\"]}\n * defaultValues={[\"id\"]}\n * onValuesChange={(values) => console.log(values)}\n * placeholder=\"Select or type to add keys\"\n * size=\"sm\"\n * width={240}\n * />\n * ```\n */\nexport const DropdownValuesInput = (props: DropdownValuesInputProps) => {\n const { defaultValues, suggestionList, onValuesChange, className } = props;\n const [values, setValues] = useState<string[]>(defaultValues ?? []);\n const [filter, setFilter] = useState<string>(\"\");\n const [isTyping, setIsTyping] = useState<boolean>(false);\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const open = Boolean(anchorEl);\n\n const showNumberOfValuesSelected = (tags: string[]) => {\n if (tags.length > 1) {\n return `${tags.length} ${props.unitName}s selected`;\n } else if (tags.length === 1) {\n return tags[0];\n }\n return \"\";\n };\n\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(event.currentTarget);\n // Focus input after menu opens\n setTimeout(() => inputRef.current?.focus(), 100);\n };\n\n const handleClose = () => {\n setAnchorEl(null);\n setIsTyping(false);\n };\n\n const handleSelect = (value: string) => {\n if (!values.includes(value)) {\n setFilter(\"\");\n setValues([...values, value]);\n onValuesChange([...values, value]);\n }\n };\n\n const handleClear = () => {\n setFilter(\"\");\n setValues([]);\n onValuesChange([]);\n };\n\n const handleRemoveValue = (value: string) => {\n setValues(values.filter((v) => v !== value));\n onValuesChange(values.filter((v) => v !== value));\n };\n\n // Filter the suggestion list without case sensitivity based on the current input\n const lowerCaseFilter = filter.toLowerCase();\n const filteredList =\n suggestionList\n ?.filter(\n (value) =>\n lowerCaseFilter === \"\" ||\n value.toLowerCase().includes(lowerCaseFilter),\n )\n .filter((value) => !values.includes(value)) ?? [];\n const limit = 10;\n\n // Size mapping for font sizes\n const getFontSize = () => {\n switch (props.size) {\n case \"2xs\":\n return \"0.625rem\";\n case \"xs\":\n return \"0.75rem\";\n case \"sm\":\n return \"0.875rem\";\n default:\n return \"0.875rem\";\n }\n };\n\n const getHeight = () => {\n switch (props.size) {\n case \"2xs\":\n return 24;\n case \"xs\":\n return 28;\n case \"sm\":\n return 32;\n default:\n return 36;\n }\n };\n\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n width: props.width,\n }}\n className={className}\n >\n <Button\n variant=\"outlined\"\n color=\"neutral\"\n size=\"small\"\n onClick={handleClick}\n disabled={props.disabled}\n sx={{\n width: \"100%\",\n height: getHeight(),\n justifyContent: \"space-between\",\n textTransform: \"none\",\n fontSize: getFontSize(),\n fontWeight: \"normal\",\n px: 1,\n }}\n >\n <Typography\n component=\"span\"\n sx={{\n fontSize: getFontSize(),\n color: values.length > 0 ? \"text.primary\" : \"text.secondary\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {showNumberOfValuesSelected(values) || props.placeholder || \"\"}\n </Typography>\n {values.length > 0 && (\n <Typography\n component=\"span\"\n onClick={(e) => {\n e.stopPropagation();\n handleClear();\n }}\n sx={{\n fontSize: getFontSize(),\n color: \"primary.main\",\n cursor: \"pointer\",\n ml: 1,\n \"&:hover\": { textDecoration: \"underline\" },\n }}\n >\n Clear\n </Typography>\n )}\n </Button>\n\n <Menu\n anchorEl={anchorEl}\n open={open}\n onClose={handleClose}\n slotProps={{\n paper: {\n sx: {\n width: props.width,\n fontSize: getFontSize(),\n },\n },\n }}\n >\n {/* Input Filter & Show Tags */}\n <Box sx={{ px: 0.5, py: 0.5 }}>\n <Box\n sx={{\n border: \"1px solid\",\n borderColor: \"divider\",\n borderRadius: 1,\n p: 0.5,\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: 0.5,\n alignItems: \"center\",\n }}\n className=\"no-track-pii-safe\"\n >\n {values.map((value) => (\n <Chip\n key={value}\n label={value}\n size=\"small\"\n onDelete={() => handleRemoveValue(value)}\n sx={{ height: 22, fontSize: getFontSize() }}\n />\n ))}\n <InputBase\n inputRef={inputRef}\n placeholder=\"Filter or add custom keys\"\n value={filter}\n onChange={(e) => {\n setFilter(e.target.value);\n setIsTyping(true);\n }}\n onKeyDown={(e) => {\n // Stop propagation to prevent MUI Menu's typeahead navigation\n // from intercepting key presses (except Escape to close menu)\n if (e.key !== \"Escape\") {\n e.stopPropagation();\n }\n\n const target = e.target as HTMLInputElement;\n const newText = target.value.trim().replace(\",\", \"\");\n switch (e.key) {\n case \",\":\n case \"Enter\":\n e.preventDefault();\n if (newText) {\n handleSelect(newText);\n setFilter(\"\");\n }\n break;\n case \"Backspace\":\n if (target.value === \"\" && values.length > 0) {\n setValues(values.slice(0, -1));\n onValuesChange(values.slice(0, -1));\n }\n break;\n default:\n break;\n }\n }}\n onBlur={() => {\n if (inputRef.current && isTyping) {\n inputRef.current.focus();\n }\n }}\n sx={{\n flex: 1,\n minWidth: 120,\n fontSize: getFontSize(),\n \"& input\": {\n p: 0.5,\n },\n }}\n />\n </Box>\n </Box>\n\n <Divider />\n\n {/* Suggestion List */}\n {filter !== \"\" && !suggestionList?.includes(filter) && (\n <MenuItem\n onClick={() => {\n handleSelect(filter);\n setIsTyping(false);\n }}\n sx={{ fontSize: getFontSize() }}\n >\n Add '{filter}' to the list\n </MenuItem>\n )}\n {filteredList.slice(0, limit).map((value, cid) => (\n <MenuItem\n key={_.uniqueId(`option-${cid}`)}\n onClick={() => {\n handleSelect(value);\n }}\n sx={{ fontSize: getFontSize() }}\n >\n {value}\n </MenuItem>\n ))}\n {filteredList.length > limit && (\n <MuiTooltip\n title=\"Please use filter to find more items\"\n placement=\"top\"\n >\n <Box px={1.5} py={0.5} color=\"text.secondary\" fontSize=\"8pt\">\n and {filteredList.length - limit} more items...\n </Box>\n </MuiTooltip>\n )}\n </Menu>\n </Box>\n );\n};\n","\"use client\";\n\n/**\n * @file HistogramDiffForm.tsx\n * @description Form component for configuring histogram diff parameters\n *\n * This component allows users to select a column for histogram diff comparison.\n * It filters columns to only show numeric columns (excluding string, boolean, and datetime types).\n */\n\nimport Box from \"@mui/material/Box\";\nimport FormControl from \"@mui/material/FormControl\";\nimport FormLabel from \"@mui/material/FormLabel\";\nimport NativeSelect from \"@mui/material/NativeSelect\";\nimport type { HistogramDiffParams } from \"../../api\";\nimport { useModelColumns } from \"../../hooks\";\nimport type { RunFormProps } from \"../run\";\n\n// ============================================================================\n// Type Utilities\n// ============================================================================\n\nfunction isStringDataType(columnType: string) {\n const stringDataTypes = [\n \"CHAR\",\n \"VARCHAR\",\n \"TINYTEXT\",\n \"TEXT\",\n \"MEDIUMTEXT\",\n \"LONGTEXT\",\n \"NCHAR\",\n \"NVARCHAR\",\n \"VARCHAR2\",\n \"NVARCHAR2\",\n \"CLOB\",\n \"NCLOB\",\n \"VARCHAR(MAX)\",\n \"XML\",\n \"JSON\",\n ];\n // Normalize columnType by removing spaces and converting to uppercase\n const normalizedType = columnType.trim().toUpperCase();\n\n // Check if columnType is in the predefined list\n if (stringDataTypes.includes(normalizedType)) {\n return true;\n }\n\n // Match types that have a length specification (e.g., VARCHAR(255))\n const regex = /^(VARCHAR|NVARCHAR|VARCHAR2|NVARCHAR2|CHAR|NCHAR)\\(\\d+\\)$/;\n return regex.test(normalizedType);\n}\n\nfunction isBooleanDataType(columnType: string) {\n const booleanDataTypes = [\n \"BOOLEAN\", // PostgreSQL, SQLite, and others with native boolean support\n \"TINYINT(1)\", // MySQL/MariaDB uses TINYINT(1) to represent boolean values\n \"BIT\", // SQL Server and others use BIT to represent boolean values, where 1 is true and 0 is false\n \"NUMBER(1)\", // Oracle uses NUMBER(1) where 1 is true and 0 is false, as it does not have a native BOOLEAN type\n \"BOOL\", // Snowflake and PostgreSQL also support BOOL as an alias for BOOLEAN\n ];\n return booleanDataTypes.includes(columnType.toUpperCase());\n}\n\nfunction isDateTimeType(columnType: string) {\n const sql_datetime_types = [\n \"DATE\",\n \"DATETIME\",\n \"TIMESTAMP\",\n \"TIME\",\n \"YEAR\", // Specific to MySQL/MariaDB\n \"DATETIME2\",\n \"SMALLDATETIME\",\n \"DATETIMEOFFSET\", // Specific to SQL Server\n \"INTERVAL\", // Common in PostgreSQL and Oracle\n \"TIMESTAMPTZ\",\n \"TIMETZ\", // Specific to PostgreSQL\n \"TIMESTAMP WITH TIME ZONE\",\n \"TIMESTAMP WITH LOCAL TIME ZONE\", // Oracle\n \"TIMESTAMP_LTZ\",\n \"TIMESTAMP_NTZ\",\n \"TIMESTAMP_TZ\", // Specific to Snowflake\n ];\n return sql_datetime_types.includes(columnType.toUpperCase());\n}\n\n// ============================================================================\n// Public Utilities\n// ============================================================================\n\n/**\n * Check if a column type supports histogram diff\n * Returns true for numeric types (excludes string, boolean, and datetime)\n */\nexport function supportsHistogramDiff(columnType: string) {\n return (\n !isStringDataType(columnType) &&\n !isBooleanDataType(columnType) &&\n !isDateTimeType(columnType)\n );\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\ntype HistogramDiffEditProps = RunFormProps<HistogramDiffParams>;\n\n/**\n * Form component for configuring histogram diff parameters\n *\n * Displays a dropdown to select a column for histogram diff comparison.\n * Only numeric columns are shown (string, boolean, and datetime types are filtered out).\n *\n * @example\n * ```tsx\n * <HistogramDiffForm\n * params={{ model: \"orders\", column_name: \"\" }}\n * onParamsChanged={(params) => setParams(params)}\n * setIsReadyToExecute={(ready) => setReady(ready)}\n * />\n * ```\n */\nexport function HistogramDiffForm({\n params,\n onParamsChanged,\n setIsReadyToExecute,\n}: HistogramDiffEditProps) {\n const {\n columns: allColumns,\n isLoading,\n error,\n } = useModelColumns(params.model);\n const columns = allColumns.filter(\n (c) =>\n !isStringDataType(c.type) &&\n !isBooleanDataType(c.type) &&\n !isDateTimeType(c.type),\n );\n\n if (isLoading) {\n return <Box>Loading...</Box>;\n }\n\n if (allColumns.length === 0 || error) {\n return (\n <Box>\n Error: Please provide the 'catalog.json' to list column\n candidates\n </Box>\n );\n }\n\n return (\n <Box sx={{ m: \"16px\" }}>\n <FormControl fullWidth disabled={columns.length === 0}>\n <FormLabel sx={{ mb: 1 }}>\n Pick a column to show Histogram Diff\n </FormLabel>\n <NativeSelect\n value={params.column_name}\n onChange={(e) => {\n const columnName = e.target.value;\n setIsReadyToExecute(!!columnName);\n const columnType =\n columns.find((c) => c.name === columnName)?.type ?? \"\";\n onParamsChanged({\n ...params,\n column_name: columnName,\n column_type: columnType,\n });\n }}\n >\n <option value=\"\">\n {columns.length !== 0\n ? \"Select column\"\n : \"No numeric column is available\"}\n </option>\n {columns.map((c) => (\n <option key={c.name} value={c.name} className=\"no-track-pii-safe\">\n {c.name} : {c.type}\n </option>\n ))}\n </NativeSelect>\n </FormControl>\n </Box>\n );\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Chip from \"@mui/material/Chip\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\n\n/**\n * Legend item for change status\n */\nexport interface ChangeStatusLegendItem {\n status: \"added\" | \"removed\" | \"modified\";\n label: string;\n description?: string;\n}\n\n/**\n * Legend item for transformation type\n */\nexport interface TransformationLegendItem {\n type: \"passthrough\" | \"renamed\" | \"derived\" | \"source\" | \"unknown\";\n label: string;\n description?: string;\n}\n\n/**\n * Props for the LineageLegend component\n */\nexport interface LineageLegendProps {\n /**\n * Type of legend to display\n */\n variant: \"changeStatus\" | \"transformation\";\n\n /**\n * Whether to show tooltips on hover\n * @default true\n */\n showTooltips?: boolean;\n\n /**\n * Optional title for the legend\n */\n title?: string;\n\n /**\n * CSS class name for additional styling\n */\n className?: string;\n}\n\n/**\n * Default change status items\n */\nconst defaultChangeStatusItems: ChangeStatusLegendItem[] = [\n { status: \"added\", label: \"Added\", description: \"Newly added resource\" },\n { status: \"removed\", label: \"Removed\", description: \"Removed resource\" },\n { status: \"modified\", label: \"Modified\", description: \"Modified resource\" },\n];\n\n/**\n * Default transformation items\n */\nconst defaultTransformationItems: TransformationLegendItem[] = [\n {\n type: \"passthrough\",\n label: \"Passthrough\",\n description: \"Column passes through unchanged\",\n },\n {\n type: \"renamed\",\n label: \"Renamed\",\n description: \"Column was renamed from source\",\n },\n {\n type: \"derived\",\n label: \"Derived\",\n description: \"Column is derived from other columns\",\n },\n { type: \"source\", label: \"Source\", description: \"Original source column\" },\n {\n type: \"unknown\",\n label: \"Unknown\",\n description: \"Transformation type could not be determined\",\n },\n];\n\n/**\n * Colors for change status indicators\n */\nconst changeStatusStyles: Record<string, { color: string; symbol: string }> = {\n added: { color: \"#22c55e\", symbol: \"+\" },\n removed: { color: \"#ef4444\", symbol: \"-\" },\n modified: { color: \"#f59e0b\", symbol: \"~\" },\n};\n\n/**\n * Colors for transformation type chips\n */\nconst transformationStyles: Record<\n string,\n { letter: string; color: \"default\" | \"warning\" | \"info\" | \"error\" }\n> = {\n passthrough: { letter: \"P\", color: \"default\" },\n renamed: { letter: \"R\", color: \"warning\" },\n derived: { letter: \"D\", color: \"warning\" },\n source: { letter: \"S\", color: \"info\" },\n unknown: { letter: \"U\", color: \"error\" },\n};\n\n/**\n * ChangeStatusIcon - Renders a change status indicator\n */\nfunction ChangeStatusIcon({\n status,\n}: {\n status: \"added\" | \"removed\" | \"modified\";\n}) {\n const style = changeStatusStyles[status];\n return (\n <Box\n sx={{\n width: 16,\n height: 16,\n borderRadius: \"50%\",\n backgroundColor: style.color,\n color: \"white\",\n fontSize: 10,\n fontWeight: \"bold\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flexShrink: 0,\n }}\n >\n {style.symbol}\n </Box>\n );\n}\n\n/**\n * TransformationChip - Renders a transformation type chip\n */\nfunction TransformationChip({\n type,\n}: {\n type: \"passthrough\" | \"renamed\" | \"derived\" | \"source\" | \"unknown\";\n}) {\n const style = transformationStyles[type];\n return (\n <Chip\n label={style.letter}\n size=\"small\"\n color={style.color}\n sx={{\n fontSize: \"8pt\",\n height: 18,\n minWidth: 18,\n \"& .MuiChip-label\": {\n px: 0.5,\n },\n }}\n />\n );\n}\n\n/**\n * LineageLegend Component\n *\n * A pure presentation component for displaying legends in lineage visualizations.\n * Supports both change status legends (added/removed/modified) and\n * transformation type legends (passthrough/renamed/derived/source/unknown).\n *\n * @example Change status legend\n * ```tsx\n * import { LineageLegend } from '@datarecce/ui/primitives';\n *\n * function MyLineageGraph() {\n * return (\n * <div style={{ position: 'relative' }}>\n * <ReactFlow nodes={nodes} edges={edges} />\n * <div style={{ position: 'absolute', bottom: 10, right: 10 }}>\n * <LineageLegend variant=\"changeStatus\" title=\"Changes\" />\n * </div>\n * </div>\n * );\n * }\n * ```\n *\n * @example Transformation type legend\n * ```tsx\n * import { LineageLegend } from '@datarecce/ui/primitives';\n *\n * function ColumnLineageGraph() {\n * return (\n * <div>\n * <ReactFlow nodes={columnNodes} edges={edges} />\n * <LineageLegend variant=\"transformation\" />\n * </div>\n * );\n * }\n * ```\n */\nexport function LineageLegend({\n variant,\n showTooltips = true,\n title,\n className,\n}: LineageLegendProps) {\n const items =\n variant === \"changeStatus\"\n ? defaultChangeStatusItems\n : defaultTransformationItems;\n\n return (\n <Box\n className={className}\n sx={{\n bgcolor: \"background.paper\",\n padding: \"12px\",\n border: \"1px solid\",\n borderColor: \"divider\",\n borderRadius: 1,\n fontSize: \"0.875rem\",\n }}\n >\n {title && (\n <Typography\n variant=\"caption\"\n sx={{\n display: \"block\",\n fontWeight: 600,\n mb: 1,\n color: \"text.secondary\",\n }}\n >\n {title}\n </Typography>\n )}\n\n {variant === \"changeStatus\" &&\n (items as ChangeStatusLegendItem[]).map((item) => {\n const content = (\n <Box\n key={item.status}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n mb: \"4px\",\n \"&:last-child\": { mb: 0 },\n }}\n >\n <ChangeStatusIcon status={item.status} />\n <Typography variant=\"body2\">{item.label}</Typography>\n </Box>\n );\n\n return showTooltips && item.description ? (\n <Tooltip\n key={item.status}\n title={item.description}\n placement=\"right\"\n >\n {content}\n </Tooltip>\n ) : (\n content\n );\n })}\n\n {variant === \"transformation\" &&\n (items as TransformationLegendItem[]).map((item) => {\n const content = (\n <Box\n key={item.type}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n mb: \"4px\",\n \"&:last-child\": { mb: 0 },\n }}\n >\n <TransformationChip type={item.type} />\n <Typography variant=\"body2\">{item.label}</Typography>\n </Box>\n );\n\n return showTooltips && item.description ? (\n <Tooltip key={item.type} title={item.description} placement=\"right\">\n {content}\n </Tooltip>\n ) : (\n content\n );\n })}\n </Box>\n );\n}\n","\"use client\";\n\n/**\n * @file LineageNode.tsx\n * @description Pure presentation component for displaying nodes in a lineage graph\n *\n * This component is designed to work with @xyflow/react and receives all data via props.\n * It does not perform any data fetching or state management - it is purely presentational.\n *\n * Features:\n * - Change status visualization (added, removed, modified, unchanged)\n * - Selection modes (normal, selecting, action_result)\n * - Interactive checkbox for multi-select\n * - Action tag display for run status\n * - Resource type and change status icons\n * - Hover menu with context actions\n * - Column container support\n *\n * Source: Enhanced from UI package primitive + OSS js/src/components/lineage/GraphNode.tsx\n */\n\nimport Box from \"@mui/material/Box\";\nimport Checkbox from \"@mui/material/Checkbox\";\nimport Stack from \"@mui/material/Stack\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport { Handle, Position } from \"@xyflow/react\";\nimport { type MouseEvent, memo, type ReactNode, useState } from \"react\";\nimport { getIconForChangeStatus, getIconForResourceType } from \"../styles\";\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\n/**\n * Change status for node visualization\n */\nexport type NodeChangeStatus = \"added\" | \"removed\" | \"modified\" | \"unchanged\";\n\n/**\n * Selection mode for the node\n */\nexport type SelectMode = \"normal\" | \"selecting\" | \"action_result\";\n\n/**\n * Change category from column-level lineage analysis\n */\nexport type ChangeCategory =\n | \"breaking\"\n | \"non_breaking\"\n | \"partial_breaking\"\n | \"unknown\";\n\n/**\n * Data structure for lineage node\n */\nexport interface LineageNodeData extends Record<string, unknown> {\n /** Display label for the node */\n label: string;\n /** Node type (model, source, seed, etc.) */\n nodeType?: string;\n /** Change status for diff visualization */\n changeStatus?: NodeChangeStatus;\n /** Whether this node is currently selected */\n isSelected?: boolean;\n /** Resource type for icon display */\n resourceType?: string;\n /** Package name */\n packageName?: string;\n /** Whether to show column-level details */\n showColumns?: boolean;\n /** Column data if showing columns */\n columns?: Array<{\n name: string;\n type?: string;\n changeStatus?: NodeChangeStatus;\n }>;\n}\n\n/**\n * Props for LineageNode component\n */\nexport interface LineageNodeProps {\n /** Node ID */\n id: string;\n /** Node data */\n data: LineageNodeData;\n /** Whether the node is selected (from React Flow) */\n selected?: boolean;\n\n // === Interactive Mode Props ===\n /** Enable interactive mode with checkbox */\n interactive?: boolean;\n /** Selection mode */\n selectMode?: SelectMode;\n /** Whether the node is selected (checkbox state) */\n isNodeSelected?: boolean;\n /** Whether the node is focused */\n isFocused?: boolean;\n /** Whether the node is highlighted */\n isHighlighted?: boolean;\n /** Whether to show content (zoom level visibility) */\n showContent?: boolean;\n\n // === Action Display Props ===\n /** Action tag to display (for action_result mode) */\n actionTag?: ReactNode;\n /** Whether to show change analysis mode */\n showChangeAnalysis?: boolean;\n /** Change category text */\n changeCategory?: ChangeCategory;\n /** Runs aggregated display component */\n runsAggregatedTag?: ReactNode;\n\n // === Layout Props ===\n /** Whether node has parent nodes (show left handle) */\n hasParents?: boolean;\n /** Whether node has child nodes (show right handle) */\n hasChildren?: boolean;\n /** Number of columns for column container height */\n columnCount?: number;\n /** Height per column in pixels */\n columnHeight?: number;\n\n // === Theme Props ===\n /** Whether dark mode is active */\n isDark?: boolean;\n\n // === Callbacks ===\n /** Callback when node is clicked */\n onNodeClick?: (nodeId: string) => void;\n /** Callback when node is double-clicked */\n onNodeDoubleClick?: (nodeId: string) => void;\n /** Callback when checkbox is clicked */\n onSelect?: (nodeId: string) => void;\n /** Callback when context menu is requested */\n onContextMenu?: (event: MouseEvent, nodeId: string) => void;\n /** Callback when impact radius button is clicked */\n onShowImpactRadius?: (nodeId: string) => void;\n}\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst CHANGE_CATEGORY_LABELS: Record<ChangeCategory, string> = {\n breaking: \"Breaking\",\n non_breaking: \"Non Breaking\",\n partial_breaking: \"Partial Breaking\",\n unknown: \"Unknown\",\n};\n\nconst DEFAULT_COLUMN_HEIGHT = 28;\n\n// =============================================================================\n// ICONS\n// =============================================================================\n\n/**\n * Kebab menu icon\n */\nconst KebabIcon = () => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 16 16\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M8 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm0 6.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm0 6.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z\" />\n </svg>\n);\n\n/**\n * Impact radius icon (dot circle)\n */\nconst ImpactRadiusIcon = () => (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 512 512\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M256 56c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m0-48C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 168c-44.183 0-80 35.817-80 80s35.817 80 80 80 80-35.817 80-80-35.817-80-80-80z\" />\n </svg>\n);\n\n// =============================================================================\n// HELPER COMPONENTS\n// =============================================================================\n\n/**\n * Node title with tooltip\n */\nfunction NodeTitle({\n name,\n color,\n resourceType,\n}: {\n name: string;\n color: string;\n resourceType?: string;\n}) {\n const tooltipTitle =\n resourceType === \"model\" ? name : `${name} (${resourceType || \"unknown\"})`;\n\n return (\n <Box\n sx={{\n flex: 1,\n color,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n <Tooltip title={tooltipTitle} placement=\"top\">\n <span>{name}</span>\n </Tooltip>\n </Box>\n );\n}\n\n// =============================================================================\n// MAIN COMPONENT\n// =============================================================================\n\n/**\n * LineageNode - Pure presentation component for displaying nodes in a lineage graph\n *\n * This component is designed to be used with @xyflow/react and receives all data via props.\n * It does not perform any data fetching or state management - it is purely presentational.\n *\n * @example Basic usage\n * ```tsx\n * <LineageNode\n * id=\"model.my_model\"\n * data={{\n * label: \"my_model\",\n * nodeType: \"model\",\n * changeStatus: \"modified\",\n * resourceType: \"model\"\n * }}\n * selected={false}\n * onNodeClick={(nodeId) => console.log(\"Clicked:\", nodeId)}\n * />\n * ```\n *\n * @example Interactive mode with selection\n * ```tsx\n * <LineageNode\n * id=\"model.my_model\"\n * data={{ label: \"my_model\", changeStatus: \"added\" }}\n * interactive\n * selectMode=\"selecting\"\n * isNodeSelected={selectedNodes.has(\"model.my_model\")}\n * onSelect={(nodeId) => toggleSelection(nodeId)}\n * />\n * ```\n *\n * @example Action result mode\n * ```tsx\n * <LineageNode\n * id=\"model.my_model\"\n * data={{ label: \"my_model\", changeStatus: \"modified\" }}\n * selectMode=\"action_result\"\n * actionTag={<ActionTag status=\"running\" progress={{ percentage: 0.5 }} />}\n * />\n * ```\n */\nfunction LineageNodeComponent({\n id,\n data,\n selected,\n // Interactive props\n interactive = false,\n selectMode = \"normal\",\n isNodeSelected = false,\n isFocused = false,\n isHighlighted = true,\n showContent = true,\n // Action display props\n actionTag,\n showChangeAnalysis = false,\n changeCategory,\n runsAggregatedTag,\n // Layout props\n hasParents = true,\n hasChildren = true,\n columnCount = 0,\n columnHeight = DEFAULT_COLUMN_HEIGHT,\n // Theme props\n isDark = false,\n // Callbacks\n onNodeClick,\n onNodeDoubleClick,\n onSelect,\n onContextMenu,\n onShowImpactRadius,\n}: LineageNodeProps) {\n const [isHovered, setIsHovered] = useState(false);\n\n const {\n label,\n changeStatus = \"unchanged\",\n isSelected: dataIsSelected,\n resourceType,\n } = data;\n\n // Use isNodeSelected prop, fall back to data.isSelected, then to selected\n const isSelected = isNodeSelected || dataIsSelected || selected || false;\n const showColumns = columnCount > 0;\n const hasAction = selectMode === \"action_result\" && actionTag;\n\n // Get icons and colors\n const {\n icon: IconChangeStatus,\n color: colorChangeStatus,\n backgroundColor: backgroundColorChangeStatus,\n } = getIconForChangeStatus(changeStatus, isDark);\n const { icon: ResourceIcon } = getIconForResourceType(resourceType);\n\n // Calculate styles based on state\n const borderWidth = \"2px\";\n const borderColor = colorChangeStatus;\n\n // Node background color logic\n const nodeBackgroundColor = (() => {\n const paperBg = isDark ? \"#1e1e1e\" : \"#ffffff\";\n\n if (showContent) {\n if (selectMode === \"selecting\") {\n return isSelected ? colorChangeStatus : paperBg;\n }\n if (selectMode === \"action_result\") {\n if (!hasAction) return paperBg;\n return isFocused || isSelected || isHovered\n ? backgroundColorChangeStatus\n : colorChangeStatus;\n }\n return isFocused || isSelected || isHovered\n ? backgroundColorChangeStatus\n : paperBg;\n }\n return isFocused || isSelected || isHovered\n ? colorChangeStatus\n : backgroundColorChangeStatus;\n })();\n\n // Text color logic\n const titleColor = (() => {\n const primaryText = isDark ? \"#ffffff\" : \"#000000\";\n const invertedText = isDark ? \"#000000\" : \"#ffffff\";\n\n if (selectMode === \"selecting\") {\n return isSelected ? invertedText : primaryText;\n }\n if (selectMode === \"action_result\") {\n return hasAction && !isSelected ? invertedText : primaryText;\n }\n return primaryText;\n })();\n\n const iconColor = (() => {\n const primaryText = isDark ? \"#ffffff\" : \"#000000\";\n const invertedText = isDark ? \"#000000\" : \"#ffffff\";\n\n if (selectMode === \"selecting\") {\n return isSelected ? invertedText : primaryText;\n }\n if (selectMode === \"action_result\") {\n return hasAction && !isSelected ? invertedText : primaryText;\n }\n return primaryText;\n })();\n\n const changeStatusIconColor = (() => {\n const primaryText = isDark ? \"#ffffff\" : \"#000000\";\n const invertedText = isDark ? \"#000000\" : \"#ffffff\";\n\n if (selectMode === \"selecting\") {\n return isSelected ? invertedText : colorChangeStatus;\n }\n if (selectMode === \"action_result\") {\n return hasAction && !isSelected ? invertedText : primaryText;\n }\n return colorChangeStatus;\n })();\n\n // Filter for dimming\n const nodeFilter = (() => {\n if (selectMode === \"action_result\") {\n return hasAction ? \"none\" : \"opacity(0.2) grayscale(50%)\";\n }\n return isHighlighted || isFocused || isSelected || isHovered\n ? \"none\"\n : \"opacity(0.2) grayscale(50%)\";\n })();\n\n const handleCheckboxClick = (e: MouseEvent) => {\n if (selectMode === \"action_result\") return;\n e.stopPropagation();\n onSelect?.(id);\n };\n\n const handleContextMenuClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onContextMenu?.(e, id);\n };\n\n const handleImpactRadiusClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onShowImpactRadius?.(id);\n };\n\n return (\n <Box\n onClick={() => onNodeClick?.(id)}\n onDoubleClick={() => onNodeDoubleClick?.(id)}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n width: 300,\n cursor: selectMode === \"selecting\" ? \"pointer\" : \"inherit\",\n transition: \"box-shadow 0.2s ease-in-out\",\n padding: 0,\n filter: nodeFilter,\n }}\n >\n {/* Main node container */}\n <Box\n sx={{\n display: \"flex\",\n borderColor,\n borderWidth,\n borderStyle: \"solid\",\n borderTopLeftRadius: 8,\n borderTopRightRadius: 8,\n borderBottomLeftRadius: showColumns ? 0 : 8,\n borderBottomRightRadius: showColumns ? 0 : 8,\n backgroundColor: nodeBackgroundColor,\n height: 60,\n }}\n >\n {/* Left panel with checkbox */}\n <Box\n sx={{\n display: \"flex\",\n bgcolor: colorChangeStatus,\n padding: interactive ? \"8px\" : \"2px\",\n borderRightWidth: borderWidth,\n borderRightStyle: \"solid\",\n borderColor: selectMode === \"selecting\" ? \"#00000020\" : borderColor,\n alignItems: \"top\",\n visibility: showContent ? \"inherit\" : \"hidden\",\n }}\n >\n {interactive && (\n <Checkbox\n checked={\n (selectMode === \"selecting\" && isSelected) ||\n (selectMode === \"action_result\" && !!hasAction)\n }\n onClick={handleCheckboxClick}\n disabled={selectMode === \"action_result\"}\n size=\"small\"\n sx={{\n padding: 0,\n color: \"inherit\",\n \"&.Mui-checked\": { color: \"inherit\" },\n }}\n />\n )}\n </Box>\n\n {/* Content area */}\n <Box\n sx={{\n display: \"flex\",\n flex: \"1 0 auto\",\n mx: 0.5,\n width: 100,\n flexDirection: \"column\",\n }}\n >\n {/* Title row */}\n <Box\n sx={{\n display: \"flex\",\n width: \"100%\",\n textAlign: \"left\",\n fontWeight: 600,\n flex: 1,\n p: 0.5,\n gap: \"5px\",\n alignItems: \"center\",\n visibility: showContent ? \"inherit\" : \"hidden\",\n }}\n >\n <NodeTitle\n name={label}\n color={titleColor}\n resourceType={resourceType}\n />\n\n {/* Hover actions vs icons */}\n {isHovered ? (\n <>\n {changeStatus === \"modified\" && onShowImpactRadius && (\n <Tooltip title=\"Show Impact Radius\" placement=\"top\">\n <Box\n onClick={handleImpactRadiusClick}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"pointer\",\n color: \"text.secondary\",\n \"&:hover\": { color: \"text.primary\" },\n }}\n >\n <ImpactRadiusIcon />\n </Box>\n </Tooltip>\n )}\n {onContextMenu && (\n <Box\n onClick={handleContextMenuClick}\n sx={{\n cursor: \"pointer\",\n color: \"text.secondary\",\n \"&:hover\": { color: \"text.primary\" },\n }}\n >\n <KebabIcon />\n </Box>\n )}\n </>\n ) : (\n <>\n {ResourceIcon && (\n <Box sx={{ fontSize: 16, color: iconColor }}>\n <ResourceIcon />\n </Box>\n )}\n {changeStatus && IconChangeStatus && (\n <Box sx={{ color: changeStatusIconColor }}>\n <IconChangeStatus />\n </Box>\n )}\n </>\n )}\n </Box>\n\n {/* Bottom row - action tags, change analysis, or runs aggregated */}\n <Box\n sx={{\n display: \"flex\",\n flex: \"1 0 auto\",\n mx: 0.5,\n flexDirection: \"column\",\n paddingBottom: 0.5,\n visibility: showContent ? \"inherit\" : \"hidden\",\n }}\n >\n <Stack direction=\"row\" spacing={1}>\n {actionTag ? (\n <>\n <Box sx={{ flexGrow: 1 }} />\n {actionTag}\n </>\n ) : showChangeAnalysis && changeCategory ? (\n <Typography\n sx={{\n height: 20,\n color: \"text.secondary\",\n fontSize: \"9pt\",\n margin: 0,\n fontWeight: 600,\n }}\n >\n {CHANGE_CATEGORY_LABELS[changeCategory]}\n </Typography>\n ) : selectMode !== \"action_result\" && runsAggregatedTag ? (\n runsAggregatedTag\n ) : null}\n </Stack>\n </Box>\n </Box>\n </Box>\n\n {/* Column container */}\n {showColumns && (\n <Box\n sx={{\n p: \"10px 10px\",\n borderColor,\n borderWidth,\n borderStyle: \"solid\",\n borderTopWidth: 0,\n borderBottomLeftRadius: 8,\n borderBottomRightRadius: 8,\n }}\n >\n <Box\n sx={{\n height: `${columnCount * columnHeight}px`,\n overflow: \"auto\",\n }}\n />\n </Box>\n )}\n\n {/* Handles */}\n {hasParents && (\n <Handle type=\"target\" position={Position.Left} isConnectable={false} />\n )}\n {hasChildren && (\n <Handle type=\"source\" position={Position.Right} isConnectable={false} />\n )}\n </Box>\n );\n}\n\nexport const LineageNode = memo(LineageNodeComponent);\nLineageNode.displayName = \"LineageNode\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport ButtonGroup from \"@mui/material/ButtonGroup\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport { type MouseEvent, memo, useState } from \"react\";\n\n/**\n * Action types available for checks\n */\nexport type CheckActionType =\n | \"run\"\n | \"approve\"\n | \"edit\"\n | \"delete\"\n | \"duplicate\"\n | \"copy\"\n | \"preset\";\n\n/**\n * Configuration for a check action\n */\nexport interface CheckAction {\n /** Action type identifier */\n type: CheckActionType;\n /** Display label */\n label: string;\n /** Icon element (optional) */\n icon?: React.ReactNode;\n /** Whether the action is disabled */\n disabled?: boolean;\n /** Tooltip for disabled state */\n disabledTooltip?: string;\n /** Whether this is a destructive action (shown in red) */\n destructive?: boolean;\n}\n\n/**\n * Props for the CheckActions component\n */\nexport interface CheckActionsProps {\n /** ID of the check these actions are for */\n checkId: string;\n /** List of primary actions (shown as buttons) */\n primaryActions?: CheckAction[];\n /** List of secondary actions (shown in dropdown menu) */\n secondaryActions?: CheckAction[];\n /** Callback when an action is triggered */\n onAction?: (checkId: string, actionType: CheckActionType) => void;\n /** Render variant */\n variant?: \"buttons\" | \"menu\" | \"combined\";\n /** Size of buttons */\n size?: \"small\" | \"medium\";\n /** Icon for menu trigger button */\n menuIcon?: React.ReactNode;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * Default menu icon (three vertical dots)\n */\nconst DefaultMenuIcon = () => (\n <Box\n component=\"span\"\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n \"& > span\": {\n width: 4,\n height: 4,\n borderRadius: \"50%\",\n backgroundColor: \"currentColor\",\n },\n }}\n >\n <span />\n <span />\n <span />\n </Box>\n);\n\n/**\n * CheckActions Component\n *\n * A pure presentation component for displaying action buttons/menu for a check.\n * Supports primary actions as buttons and secondary actions in a dropdown menu.\n *\n * @example Basic usage with buttons\n * ```tsx\n * import { CheckActions } from '@datarecce/ui/primitives';\n *\n * <CheckActions\n * checkId={check.id}\n * primaryActions={[\n * { type: 'run', label: 'Run' },\n * { type: 'approve', label: 'Approve' },\n * ]}\n * onAction={(id, action) => handleAction(id, action)}\n * />\n * ```\n *\n * @example Combined buttons and menu\n * ```tsx\n * <CheckActions\n * checkId={check.id}\n * variant=\"combined\"\n * primaryActions={[\n * { type: 'run', label: 'Run', icon: <PlayIcon /> },\n * ]}\n * secondaryActions={[\n * { type: 'duplicate', label: 'Duplicate' },\n * { type: 'copy', label: 'Copy Markdown' },\n * { type: 'delete', label: 'Delete', destructive: true },\n * ]}\n * onAction={(id, action) => handleAction(id, action)}\n * />\n * ```\n *\n * @example Menu only\n * ```tsx\n * <CheckActions\n * checkId={check.id}\n * variant=\"menu\"\n * secondaryActions={allActions}\n * onAction={(id, action) => handleAction(id, action)}\n * />\n * ```\n */\nfunction CheckActionsComponent({\n checkId,\n primaryActions = [],\n secondaryActions = [],\n onAction,\n variant = \"combined\",\n size = \"small\",\n menuIcon,\n className,\n}: CheckActionsProps) {\n const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);\n const menuOpen = Boolean(anchorEl);\n\n const handleMenuClick = (event: MouseEvent<HTMLElement>) => {\n event.stopPropagation();\n setAnchorEl(event.currentTarget);\n };\n\n const handleMenuClose = () => {\n setAnchorEl(null);\n };\n\n const handleAction = (actionType: CheckActionType) => {\n handleMenuClose();\n onAction?.(checkId, actionType);\n };\n\n const renderActionButton = (action: CheckAction) => {\n const button = (\n <Button\n key={action.type}\n variant=\"outlined\"\n size={size}\n disabled={action.disabled}\n onClick={() => handleAction(action.type)}\n startIcon={action.icon}\n color={action.destructive ? \"error\" : \"inherit\"}\n >\n {action.label}\n </Button>\n );\n\n if (action.disabled && action.disabledTooltip) {\n return (\n <Tooltip key={action.type} title={action.disabledTooltip}>\n <span>{button}</span>\n </Tooltip>\n );\n }\n\n return button;\n };\n\n // Menu-only variant\n if (variant === \"menu\") {\n const allActions = [...primaryActions, ...secondaryActions];\n return (\n <Box className={className}>\n <IconButton onClick={handleMenuClick} size={size}>\n {menuIcon || <DefaultMenuIcon />}\n </IconButton>\n <Menu anchorEl={anchorEl} open={menuOpen} onClose={handleMenuClose}>\n {allActions.map((action) => (\n <MenuItem\n key={action.type}\n onClick={() => handleAction(action.type)}\n disabled={action.disabled}\n sx={{\n color: action.destructive ? \"error.main\" : \"inherit\",\n }}\n >\n {action.icon && (\n <Box component=\"span\" sx={{ mr: 1, display: \"flex\" }}>\n {action.icon}\n </Box>\n )}\n {action.label}\n </MenuItem>\n ))}\n </Menu>\n </Box>\n );\n }\n\n // Buttons-only variant\n if (variant === \"buttons\") {\n return (\n <Box className={className} sx={{ display: \"flex\", gap: 1 }}>\n <ButtonGroup size={size} variant=\"outlined\">\n {primaryActions.map(renderActionButton)}\n </ButtonGroup>\n </Box>\n );\n }\n\n // Combined variant (buttons + menu)\n return (\n <Box className={className} sx={{ display: \"flex\", gap: 1 }}>\n {/* Primary action buttons */}\n {primaryActions.length > 0 && (\n <ButtonGroup size={size} variant=\"outlined\">\n {primaryActions.map(renderActionButton)}\n </ButtonGroup>\n )}\n\n {/* Secondary actions menu */}\n {secondaryActions.length > 0 && (\n <>\n <IconButton onClick={handleMenuClick} size={size}>\n {menuIcon || <DefaultMenuIcon />}\n </IconButton>\n <Menu anchorEl={anchorEl} open={menuOpen} onClose={handleMenuClose}>\n {secondaryActions.map((action) => (\n <MenuItem\n key={action.type}\n onClick={() => handleAction(action.type)}\n disabled={action.disabled}\n sx={{\n color: action.destructive ? \"error.main\" : \"inherit\",\n }}\n >\n {action.icon && (\n <Box component=\"span\" sx={{ mr: 1, display: \"flex\" }}>\n {action.icon}\n </Box>\n )}\n {action.label}\n </MenuItem>\n ))}\n </Menu>\n </>\n )}\n </Box>\n );\n}\n\nexport const CheckActions = memo(CheckActionsComponent);\nCheckActions.displayName = \"CheckActions\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport TextField from \"@mui/material/TextField\";\nimport {\n type ChangeEvent,\n memo,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\n/**\n * Props for the CheckBreadcrumb component\n */\nexport interface CheckBreadcrumbProps {\n /** Current name value */\n name: string;\n /** Callback when name is saved */\n onNameChange?: (name: string) => void;\n /** Placeholder text when empty */\n placeholder?: string;\n /** Whether editing is disabled */\n disabled?: boolean;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CheckBreadcrumb Component\n *\n * A pure presentation component for displaying and editing a check name inline.\n * Supports click-to-edit behavior with Enter to save and Escape to cancel.\n * Also commits changes when clicking outside the input.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckBreadcrumb } from '@datarecce/ui/primitives';\n *\n * function CheckHeader({ check }) {\n * return (\n * <CheckBreadcrumb\n * name={check.name}\n * onNameChange={(name) => updateCheck(check.id, { name })}\n * />\n * );\n * }\n * ```\n *\n * @example Disabled state\n * ```tsx\n * <CheckBreadcrumb\n * name={check.name}\n * disabled\n * placeholder=\"Unnamed check\"\n * />\n * ```\n *\n * @example With placeholder\n * ```tsx\n * <CheckBreadcrumb\n * name=\"\"\n * placeholder=\"Enter check name...\"\n * onNameChange={(name) => updateCheck(check.id, { name })}\n * />\n * ```\n */\nfunction CheckBreadcrumbComponent({\n name,\n onNameChange,\n placeholder = \"Unnamed check\",\n disabled = false,\n className,\n}: CheckBreadcrumbProps) {\n const [isEditing, setIsEditing] = useState(false);\n const [editValue, setEditValue] = useState(name);\n const editInputRef = useRef<HTMLInputElement>(null);\n\n // Sync edit value when external name changes\n useEffect(() => {\n setEditValue(name);\n }, [name]);\n\n const handleClick = useCallback(() => {\n if (!disabled) {\n setEditValue(name);\n setIsEditing(true);\n }\n }, [disabled, name]);\n\n const handleCommit = useCallback(() => {\n const trimmedValue = editValue.trim();\n if (trimmedValue && trimmedValue !== name) {\n onNameChange?.(trimmedValue);\n }\n setIsEditing(false);\n }, [editValue, name, onNameChange]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleCommit();\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n setEditValue(name);\n setIsEditing(false);\n }\n },\n [handleCommit, name],\n );\n\n const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {\n setEditValue(event.target.value);\n }, []);\n\n // Handle click outside to commit changes\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n editInputRef.current &&\n !editInputRef.current.contains(event.target as Node | null)\n ) {\n handleCommit();\n }\n };\n\n if (isEditing) {\n document.addEventListener(\"mousedown\", handleClickOutside);\n }\n\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n };\n }, [isEditing, handleCommit]);\n\n // Focus input when entering edit mode\n useEffect(() => {\n if (isEditing && editInputRef.current) {\n editInputRef.current.focus();\n editInputRef.current.select();\n }\n }, [isEditing]);\n\n return (\n <Box\n className={className}\n sx={{\n flex: \"2\",\n fontSize: \"1rem\",\n fontWeight: 500,\n overflow: \"hidden\",\n color: \"text.primary\",\n cursor: disabled ? \"default\" : \"pointer\",\n }}\n >\n {isEditing ? (\n <TextField\n inputRef={editInputRef}\n value={editValue}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n size=\"small\"\n sx={{ width: \"100%\" }}\n variant=\"outlined\"\n />\n ) : (\n <Box\n sx={{\n flex: \"0 1 auto\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n \"&:hover\": {\n textDecoration: disabled ? \"none\" : \"underline\",\n },\n }}\n onClick={handleClick}\n >\n {name || (\n <Box\n component=\"span\"\n sx={{ color: \"text.secondary\", fontStyle: \"italic\" }}\n >\n {placeholder}\n </Box>\n )}\n </Box>\n )}\n </Box>\n );\n}\n\nexport const CheckBreadcrumb = memo(CheckBreadcrumbComponent);\nCheckBreadcrumb.displayName = \"CheckBreadcrumb\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Checkbox from \"@mui/material/Checkbox\";\nimport Chip from \"@mui/material/Chip\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport { type MouseEvent, memo } from \"react\";\n\n/**\n * Check type categories for icon display\n */\nexport type CheckType =\n | \"query\"\n | \"query_base\"\n | \"query_diff\"\n | \"schema_diff\"\n | \"lineage_diff\"\n | \"profile\"\n | \"profile_diff\"\n | \"row_count\"\n | \"row_count_diff\"\n | \"value_diff\"\n | \"histogram_diff\"\n | \"top_k_diff\"\n | \"simple\";\n\n/**\n * Status of a check run\n */\nexport type CheckRunStatus = \"pending\" | \"running\" | \"success\" | \"error\";\n\n/**\n * Data structure for check display\n */\nexport interface CheckCardData {\n /** Unique check identifier */\n id: string;\n /** Display name of the check */\n name: string;\n /** Type of check for icon display */\n type: CheckType;\n /** Whether the check is approved */\n isApproved?: boolean;\n /** Run status of the check */\n runStatus?: CheckRunStatus;\n /** Whether the check is a preset */\n isPreset?: boolean;\n}\n\n/**\n * Props for the CheckCard component\n */\nexport interface CheckCardProps {\n /** Check data to display */\n check: CheckCardData;\n /** Whether this card is currently selected */\n isSelected?: boolean;\n /** Callback when card is clicked */\n onClick?: (checkId: string) => void;\n /** Callback when approval checkbox is toggled */\n onApprovalChange?: (checkId: string, isApproved: boolean) => void;\n /** Whether approval checkbox is disabled */\n disableApproval?: boolean;\n /** Tooltip text for disabled approval */\n disabledApprovalTooltip?: string;\n /** Whether the entire card is disabled */\n disabled?: boolean;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * Get icon symbol for check type\n */\nfunction getCheckTypeIcon(type: CheckType): string {\n const icons: Record<CheckType, string> = {\n query: \"Q\",\n query_base: \"Q\",\n query_diff: \"QD\",\n schema_diff: \"SD\",\n lineage_diff: \"LD\",\n profile: \"P\",\n profile_diff: \"PD\",\n row_count: \"R\",\n row_count_diff: \"RD\",\n value_diff: \"VD\",\n histogram_diff: \"H\",\n top_k_diff: \"TK\",\n simple: \"•\",\n };\n return icons[type] || \"?\";\n}\n\n/**\n * Get color for check type icon\n */\nfunction getCheckTypeColor(type: CheckType): string {\n const colors: Record<CheckType, string> = {\n query: \"#3b82f6\", // blue\n query_base: \"#3b82f6\", // blue\n query_diff: \"#8b5cf6\", // purple\n schema_diff: \"#06b6d4\", // cyan\n lineage_diff: \"#10b981\", // emerald\n profile: \"#f59e0b\", // amber\n profile_diff: \"#f97316\", // orange\n row_count: \"#ec4899\", // pink\n row_count_diff: \"#ef4444\", // red\n value_diff: \"#6366f1\", // indigo\n histogram_diff: \"#14b8a6\", // teal\n top_k_diff: \"#a855f7\", // violet\n simple: \"#6b7280\", // gray\n };\n return colors[type] || \"#6b7280\";\n}\n\n/**\n * Get status color for run status\n * Note: Prefixed with underscore as it's reserved for future use\n */\nfunction _getStatusColor(status?: CheckRunStatus): string {\n if (!status) return \"transparent\";\n const colors: Record<CheckRunStatus, string> = {\n pending: \"#6b7280\", // gray\n running: \"#3b82f6\", // blue\n success: \"#22c55e\", // green\n error: \"#ef4444\", // red\n };\n return colors[status];\n}\n\n/**\n * Get status label for tooltip\n * Note: Prefixed with underscore as it's reserved for future use\n */\nfunction _getStatusLabel(status?: CheckRunStatus): string {\n if (!status) return \"\";\n const labels: Record<CheckRunStatus, string> = {\n pending: \"Pending\",\n running: \"Running\",\n success: \"Ready\",\n error: \"Error\",\n };\n return labels[status];\n}\n\n/**\n * CheckCard Component\n *\n * A pure presentation component for displaying a single check in a list.\n * Shows check type icon, name, approval status, and run status.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckCard } from '@datarecce/ui/primitives';\n *\n * function CheckListItem({ check }) {\n * return (\n * <CheckCard\n * check={{\n * id: check.check_id,\n * name: check.name,\n * type: check.type,\n * isApproved: check.is_checked,\n * }}\n * isSelected={selectedId === check.check_id}\n * onClick={(id) => setSelectedId(id)}\n * onApprovalChange={(id, approved) => updateCheck(id, { is_checked: approved })}\n * />\n * );\n * }\n * ```\n *\n * @example Disabled state\n * ```tsx\n * <CheckCard\n * check={check}\n * disableApproval\n * disabledApprovalTooltip=\"Run the check first to enable approval\"\n * />\n * ```\n */\nfunction CheckCardComponent({\n check,\n isSelected = false,\n onClick,\n onApprovalChange,\n disableApproval = false,\n disabledApprovalTooltip,\n disabled = false,\n className,\n}: CheckCardProps) {\n const handleClick = () => {\n if (!disabled && onClick) {\n onClick(check.id);\n }\n };\n\n const handleApprovalClick = (e: MouseEvent) => {\n e.stopPropagation();\n };\n\n const handleApprovalChange = (\n _e: React.ChangeEvent<HTMLInputElement>,\n checked: boolean,\n ) => {\n if (onApprovalChange) {\n onApprovalChange(check.id, checked);\n }\n };\n\n const approvalCheckbox = (\n <Checkbox\n checked={check.isApproved ?? false}\n onChange={handleApprovalChange}\n onClick={handleApprovalClick}\n disabled={disableApproval || disabled}\n size=\"small\"\n sx={{\n padding: \"4px\",\n \"&.Mui-disabled\": {\n opacity: 0.5,\n },\n }}\n />\n );\n\n return (\n <Box\n className={className}\n onClick={handleClick}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 1,\n padding: \"8px 12px\",\n cursor: disabled ? \"default\" : \"pointer\",\n borderLeft: isSelected ? \"3px solid\" : \"3px solid transparent\",\n borderLeftColor: isSelected ? \"primary.main\" : \"transparent\",\n backgroundColor: isSelected ? \"action.selected\" : \"transparent\",\n opacity: disabled ? 0.6 : 1,\n transition: \"background-color 0.15s ease\",\n \"&:hover\": {\n backgroundColor: disabled\n ? \"transparent\"\n : isSelected\n ? \"action.selected\"\n : \"action.hover\",\n },\n }}\n >\n {/* Type icon */}\n <Chip\n label={getCheckTypeIcon(check.type)}\n size=\"small\"\n sx={{\n minWidth: 32,\n height: 24,\n fontSize: \"0.7rem\",\n fontWeight: 600,\n backgroundColor: `${getCheckTypeColor(check.type)}20`,\n color: getCheckTypeColor(check.type),\n \"& .MuiChip-label\": {\n px: 1,\n },\n }}\n />\n\n {/* Check name */}\n <Typography\n variant=\"body2\"\n sx={{\n flexGrow: 1,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {check.name}\n </Typography>\n\n {/* Run status indicator */}\n {/*{check.runStatus && (*/}\n {/* <Tooltip title={getStatusLabel(check.runStatus)}>*/}\n {/* <Box*/}\n {/* sx={{*/}\n {/* width: 8,*/}\n {/* height: 8,*/}\n {/* borderRadius: \"50%\",*/}\n {/* backgroundColor: getStatusColor(check.runStatus),*/}\n {/* flexShrink: 0,*/}\n {/* }}*/}\n {/* />*/}\n {/* </Tooltip>*/}\n {/*)}*/}\n\n {/* Preset badge */}\n {check.isPreset && (\n <Chip\n label=\"Preset\"\n size=\"small\"\n variant=\"outlined\"\n sx={{\n height: 20,\n fontSize: \"0.65rem\",\n }}\n />\n )}\n\n {/* Approval checkbox */}\n {disableApproval && disabledApprovalTooltip ? (\n <Tooltip title={disabledApprovalTooltip}>\n <span>{approvalCheckbox}</span>\n </Tooltip>\n ) : (\n approvalCheckbox\n )}\n </Box>\n );\n}\n\nexport const CheckCard = memo(CheckCardComponent);\nCheckCard.displayName = \"CheckCard\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Link from \"@mui/material/Link\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport Typography from \"@mui/material/Typography\";\nimport {\n type KeyboardEvent,\n memo,\n useCallback,\n useEffect,\n useState,\n} from \"react\";\n\n/**\n * Props for the CheckDescription component\n */\nexport interface CheckDescriptionProps {\n /** Current description value */\n value?: string;\n /** Callback when description is saved */\n onChange?: (value?: string) => void;\n /** Placeholder text when empty */\n placeholder?: string;\n /** Whether editing is disabled */\n disabled?: boolean;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CheckDescription Component\n *\n * A pure presentation component for displaying and editing check descriptions.\n * Supports click-to-edit with Cmd+Enter (Mac) or Ctrl+Enter (Windows) to save.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckDescription } from '@datarecce/ui/primitives';\n *\n * function CheckDetail({ check }) {\n * return (\n * <CheckDescription\n * value={check.description}\n * onChange={(desc) => updateCheck(check.id, { description: desc })}\n * placeholder=\"Add a description...\"\n * />\n * );\n * }\n * ```\n *\n * @example Disabled state\n * ```tsx\n * <CheckDescription\n * value={check.description}\n * disabled\n * placeholder=\"Description not available\"\n * />\n * ```\n */\nfunction CheckDescriptionComponent({\n value,\n onChange,\n placeholder = \"Add description here\",\n disabled = false,\n className,\n}: CheckDescriptionProps) {\n const [isEditing, setIsEditing] = useState(false);\n const [editValue, setEditValue] = useState(value ?? \"\");\n\n // Sync edit value when external value changes\n useEffect(() => {\n setEditValue(value ?? \"\");\n }, [value]);\n\n const handleStartEdit = useCallback(() => {\n if (!disabled) {\n setIsEditing(true);\n setEditValue(value ?? \"\");\n }\n }, [disabled, value]);\n\n const handleSave = useCallback(() => {\n const trimmedValue = editValue.trim();\n onChange?.(trimmedValue || undefined);\n setIsEditing(false);\n }, [editValue, onChange]);\n\n const handleCancel = useCallback(() => {\n setEditValue(value ?? \"\");\n setIsEditing(false);\n }, [value]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n // Cmd+Enter (Mac) or Ctrl+Enter (Windows) to save\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSave();\n }\n // Escape to cancel\n if (e.key === \"Escape\") {\n e.preventDefault();\n handleCancel();\n }\n },\n [handleSave, handleCancel],\n );\n\n // Editing mode\n if (isEditing) {\n return (\n <Box className={className} sx={{ height: \"100%\" }}>\n <TextField\n multiline\n fullWidth\n minRows={3}\n value={editValue}\n onChange={(e) => setEditValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n autoFocus\n sx={{\n \"& .MuiInputBase-root\": {\n fontSize: \"0.875rem\",\n },\n }}\n />\n <Stack direction=\"row\" spacing={1} sx={{ mt: 1 }}>\n <Button variant=\"contained\" size=\"small\" onClick={handleSave}>\n Update\n </Button>\n <Link\n component=\"button\"\n variant=\"body2\"\n onClick={handleCancel}\n sx={{ cursor: \"pointer\" }}\n >\n Cancel\n </Link>\n </Stack>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: \"block\", mt: 0.5 }}\n >\n {navigator.platform.includes(\"Mac\") ? \"⌘\" : \"Ctrl\"}+Enter to save,\n Escape to cancel\n </Typography>\n </Box>\n );\n }\n\n // View mode\n return (\n <Box\n className={className}\n onClick={handleStartEdit}\n sx={{\n height: \"100%\",\n overflow: \"auto\",\n cursor: disabled ? \"default\" : \"pointer\",\n padding: 1,\n borderRadius: 1,\n \"&:hover\": {\n backgroundColor: disabled ? \"transparent\" : \"action.hover\",\n },\n }}\n >\n {value ? (\n <Typography\n variant=\"body2\"\n sx={{\n whiteSpace: \"pre-wrap\",\n wordBreak: \"break-word\",\n }}\n >\n {value}\n </Typography>\n ) : (\n <Typography\n variant=\"body2\"\n sx={{\n color: \"text.secondary\",\n fontStyle: \"italic\",\n }}\n >\n {placeholder}\n </Typography>\n )}\n </Box>\n );\n}\n\nexport const CheckDescription = memo(CheckDescriptionComponent);\nCheckDescription.displayName = \"CheckDescription\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Divider from \"@mui/material/Divider\";\nimport Tab from \"@mui/material/Tab\";\nimport Tabs from \"@mui/material/Tabs\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode, useState } from \"react\";\n\nimport {\n type CheckAction,\n CheckActions,\n type CheckActionType,\n} from \"./CheckActions\";\nimport { CheckDescription } from \"./CheckDescription\";\n\n/**\n * Tab configuration for CheckDetail\n */\nexport interface CheckDetailTab {\n /** Unique tab identifier */\n id: string;\n /** Tab label */\n label: string;\n /** Tab content */\n content: ReactNode;\n}\n\n/**\n * Props for the CheckDetail component\n */\nexport interface CheckDetailProps {\n /** Check ID */\n checkId: string;\n /** Check name (displayed as header) */\n name: string;\n /** Check type for display */\n type: string;\n /** Check description */\n description?: string;\n /** Whether the check is approved */\n isApproved?: boolean;\n /** Tabs to display (result, query, etc.) */\n tabs?: CheckDetailTab[];\n /** Default selected tab ID */\n defaultTab?: string;\n /** Primary actions for the check */\n primaryActions?: CheckAction[];\n /** Secondary actions for the check */\n secondaryActions?: CheckAction[];\n /** Callback when an action is triggered */\n onAction?: (checkId: string, actionType: CheckActionType) => void;\n /** Callback when description changes */\n onDescriptionChange?: (description?: string) => void;\n /** Callback when name changes */\n onNameChange?: (name: string) => void;\n /** Whether editing is disabled */\n disabled?: boolean;\n /** Optional header content (breadcrumbs, etc.) */\n headerContent?: ReactNode;\n /** Optional sidebar content */\n sidebarContent?: ReactNode;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CheckDetail Component\n *\n * A pure presentation component for displaying detailed information\n * about a single check with tabs, actions, and editable fields.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckDetail } from '@datarecce/ui/primitives';\n *\n * function CheckPage({ check, run }) {\n * return (\n * <CheckDetail\n * checkId={check.id}\n * name={check.name}\n * type={check.type}\n * description={check.description}\n * isApproved={check.is_checked}\n * tabs={[\n * { id: 'result', label: 'Result', content: <ResultView run={run} /> },\n * { id: 'query', label: 'Query', content: <QueryView sql={check.params.sql} /> },\n * ]}\n * primaryActions={[\n * { type: 'run', label: 'Run' },\n * { type: 'approve', label: check.is_checked ? 'Approved' : 'Pending' },\n * ]}\n * secondaryActions={[\n * { type: 'delete', label: 'Delete', destructive: true },\n * ]}\n * onAction={(id, action) => handleAction(id, action)}\n * onDescriptionChange={(desc) => updateCheck(id, { description: desc })}\n * />\n * );\n * }\n * ```\n *\n * @example With sidebar\n * ```tsx\n * <CheckDetail\n * {...props}\n * sidebarContent={<CheckTimeline checkId={check.id} />}\n * />\n * ```\n */\nfunction CheckDetailComponent({\n checkId,\n name,\n type,\n description,\n isApproved = false,\n tabs = [],\n defaultTab,\n primaryActions = [],\n secondaryActions = [],\n onAction,\n onDescriptionChange,\n onNameChange,\n disabled = false,\n headerContent,\n sidebarContent,\n className,\n}: CheckDetailProps) {\n const [selectedTab, setSelectedTab] = useState(defaultTab ?? tabs[0]?.id);\n const [isEditingName, setIsEditingName] = useState(false);\n const [editedName, setEditedName] = useState(name);\n\n const handleNameClick = () => {\n if (!disabled) {\n setIsEditingName(true);\n setEditedName(name);\n }\n };\n\n const handleNameSave = () => {\n if (editedName.trim() && editedName !== name) {\n onNameChange?.(editedName.trim());\n }\n setIsEditingName(false);\n };\n\n const handleNameKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\") {\n handleNameSave();\n } else if (e.key === \"Escape\") {\n setEditedName(name);\n setIsEditingName(false);\n }\n };\n\n const selectedTabContent = tabs.find((t) => t.id === selectedTab)?.content;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n height: \"100%\",\n overflow: \"hidden\",\n }}\n >\n {/* Header */}\n <Box sx={{ p: 2, borderBottom: 1, borderColor: \"divider\" }}>\n {headerContent}\n\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"flex-start\",\n justifyContent: \"space-between\",\n gap: 2,\n }}\n >\n {/* Name */}\n <Box sx={{ flexGrow: 1, minWidth: 0 }}>\n {isEditingName ? (\n <input\n type=\"text\"\n value={editedName}\n onChange={(e) => setEditedName(e.target.value)}\n onBlur={handleNameSave}\n onKeyDown={handleNameKeyDown}\n autoFocus\n style={{\n fontSize: \"1.25rem\",\n fontWeight: 500,\n border: \"none\",\n borderBottom: \"2px solid\",\n borderColor: \"currentColor\",\n outline: \"none\",\n background: \"transparent\",\n width: \"100%\",\n }}\n />\n ) : (\n <Typography\n variant=\"h6\"\n onClick={handleNameClick}\n sx={{\n cursor: disabled ? \"default\" : \"pointer\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n \"&:hover\": {\n textDecoration: disabled ? \"none\" : \"underline\",\n },\n }}\n >\n {name}\n </Typography>\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {type} {isApproved && \"• Approved\"}\n </Typography>\n </Box>\n\n {/* Actions */}\n <CheckActions\n checkId={checkId}\n primaryActions={primaryActions}\n secondaryActions={secondaryActions}\n onAction={onAction}\n />\n </Box>\n </Box>\n\n {/* Main content area */}\n <Box sx={{ display: \"flex\", flexGrow: 1, overflow: \"hidden\" }}>\n {/* Main panel */}\n <Box\n sx={{\n flexGrow: 1,\n display: \"flex\",\n flexDirection: \"column\",\n overflow: \"hidden\",\n }}\n >\n {/* Description */}\n <Box sx={{ p: 2, borderBottom: 1, borderColor: \"divider\" }}>\n <Typography variant=\"subtitle2\" sx={{ mb: 1 }}>\n Description\n </Typography>\n <CheckDescription\n value={description}\n onChange={onDescriptionChange}\n disabled={disabled}\n />\n </Box>\n\n {/* Tabs */}\n {tabs.length > 0 && (\n <>\n <Box sx={{ borderBottom: 1, borderColor: \"divider\" }}>\n <Tabs\n value={selectedTab}\n onChange={(_e, newValue) => setSelectedTab(newValue)}\n >\n {tabs.map((tab) => (\n <Tab key={tab.id} value={tab.id} label={tab.label} />\n ))}\n </Tabs>\n </Box>\n\n {/* Tab content */}\n <Box sx={{ flexGrow: 1, overflow: \"auto\", p: 2 }}>\n {selectedTabContent}\n </Box>\n </>\n )}\n </Box>\n\n {/* Sidebar */}\n {sidebarContent && (\n <>\n <Divider orientation=\"vertical\" flexItem />\n <Box\n sx={{\n width: 350,\n flexShrink: 0,\n overflow: \"auto\",\n }}\n >\n {sidebarContent}\n </Box>\n </>\n )}\n </Box>\n </Box>\n );\n}\n\nexport const CheckDetail = memo(CheckDetailComponent);\nCheckDetail.displayName = \"CheckDetail\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode } from \"react\";\n\n/**\n * Props for the CheckEmptyState component\n */\nexport interface CheckEmptyStateProps {\n /** Main title text */\n title?: string;\n /** Description text */\n description?: string;\n /** Icon element to display */\n icon?: ReactNode;\n /** Primary action button text */\n actionText?: string;\n /** Callback when primary action is clicked */\n onAction?: () => void;\n /** Whether the action is loading */\n isLoading?: boolean;\n /** Optional helper text below the action */\n helperText?: string;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CheckEmptyState Component\n *\n * A pure presentation component for displaying an empty state\n * when no checks exist in the list.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckEmptyState } from '@datarecce/ui/primitives';\n *\n * function MyCheckList() {\n * if (checks.length === 0) {\n * return (\n * <CheckEmptyState\n * title=\"No checks yet\"\n * description=\"Create your first check to start validating data\"\n * actionText=\"Create Schema Diff Check\"\n * onAction={() => createSchemaDiffCheck()}\n * />\n * );\n * }\n * return <CheckList checks={checks} />;\n * }\n * ```\n *\n * @example Custom icon\n * ```tsx\n * import { TbChecklist } from 'react-icons/tb';\n *\n * <CheckEmptyState\n * icon={<TbChecklist size={48} />}\n * title=\"No checks found\"\n * description=\"Checks help you validate your data changes\"\n * />\n * ```\n */\nfunction CheckEmptyStateComponent({\n title = \"No checks yet\",\n description = \"Create your first check to start validating data changes\",\n icon,\n actionText,\n onAction,\n isLoading = false,\n helperText,\n className,\n}: CheckEmptyStateProps) {\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n textAlign: \"center\",\n padding: 4,\n minHeight: 300,\n }}\n >\n <Stack spacing={2} alignItems=\"center\">\n {/* Icon */}\n {icon && (\n <Box\n sx={{\n color: \"text.secondary\",\n fontSize: 48,\n mb: 1,\n }}\n >\n {icon}\n </Box>\n )}\n\n {/* Title */}\n <Typography\n variant=\"h6\"\n sx={{\n fontWeight: 500,\n color: \"text.primary\",\n }}\n >\n {title}\n </Typography>\n\n {/* Description */}\n <Typography\n variant=\"body2\"\n sx={{\n color: \"text.secondary\",\n maxWidth: 400,\n }}\n >\n {description}\n </Typography>\n\n {/* Action button */}\n {actionText && onAction && (\n <Button\n variant=\"contained\"\n onClick={onAction}\n disabled={isLoading}\n sx={{ mt: 2 }}\n >\n {isLoading ? \"Creating...\" : actionText}\n </Button>\n )}\n\n {/* Helper text */}\n {helperText && (\n <Typography\n variant=\"caption\"\n sx={{\n color: \"text.secondary\",\n mt: 1,\n }}\n >\n {helperText}\n </Typography>\n )}\n </Stack>\n </Box>\n );\n}\n\nexport const CheckEmptyState = memo(CheckEmptyStateComponent);\nCheckEmptyState.displayName = \"CheckEmptyState\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport List from \"@mui/material/List\";\nimport ListItem from \"@mui/material/ListItem\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo } from \"react\";\n\nimport { CheckCard, type CheckCardData } from \"./CheckCard\";\n\n/**\n * Props for the CheckList component\n */\nexport interface CheckListProps {\n /** Array of checks to display */\n checks: CheckCardData[];\n /** Currently selected check ID */\n selectedId?: string | null;\n /** Callback when a check is selected */\n onCheckSelect?: (checkId: string) => void;\n /** Callback when check approval status changes */\n onApprovalChange?: (checkId: string, isApproved: boolean) => void;\n /** Callback when checks are reordered (for drag-drop implementations) */\n onReorder?: (sourceIndex: number, destinationIndex: number) => void;\n /** Whether approval is disabled for all checks */\n disableApproval?: boolean;\n /** Tooltip for disabled approval */\n disabledApprovalTooltip?: string;\n /** Optional title for the list */\n title?: string;\n /** Optional CSS class name */\n className?: string;\n /** Whether the list is loading */\n isLoading?: boolean;\n /** Content to show when list is empty */\n emptyContent?: React.ReactNode;\n}\n\n/**\n * CheckList Component\n *\n * A pure presentation component for displaying a list of checks.\n * This component does not include drag-and-drop functionality -\n * implement that at the consumer level if needed.\n *\n * @example Basic usage\n * ```tsx\n * import { CheckList } from '@datarecce/ui/primitives';\n *\n * function MyCheckList({ checks }) {\n * const [selectedId, setSelectedId] = useState(null);\n *\n * return (\n * <CheckList\n * checks={checks.map(c => ({\n * id: c.check_id,\n * name: c.name,\n * type: c.type,\n * isApproved: c.is_checked,\n * }))}\n * selectedId={selectedId}\n * onCheckSelect={setSelectedId}\n * onApprovalChange={(id, approved) => updateCheck(id, approved)}\n * />\n * );\n * }\n * ```\n *\n * @example With empty state\n * ```tsx\n * <CheckList\n * checks={[]}\n * emptyContent={<CheckEmptyState onCreateFirst={() => createCheck()} />}\n * />\n * ```\n */\nfunction CheckListComponent({\n checks,\n selectedId,\n onCheckSelect,\n onApprovalChange,\n disableApproval = false,\n disabledApprovalTooltip,\n title,\n className,\n isLoading = false,\n emptyContent,\n}: CheckListProps) {\n // Loading state\n if (isLoading) {\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: 200,\n }}\n >\n <Typography color=\"text.secondary\">Loading checks...</Typography>\n </Box>\n );\n }\n\n // Empty state\n if (checks.length === 0) {\n return (\n <Box className={className}>\n {title && (\n <Typography\n variant=\"subtitle2\"\n sx={{ px: 2, py: 1, color: \"text.secondary\" }}\n >\n {title}\n </Typography>\n )}\n {emptyContent || (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: 200,\n }}\n >\n <Typography color=\"text.secondary\">No checks</Typography>\n </Box>\n )}\n </Box>\n );\n }\n\n return (\n <Box className={className} sx={{ height: \"100%\", overflow: \"auto\" }}>\n {title && (\n <Typography\n variant=\"subtitle2\"\n sx={{ px: 2, py: 1, color: \"text.secondary\" }}\n >\n {title}\n </Typography>\n )}\n <List disablePadding>\n {checks.map((check) => (\n <ListItem key={check.id} disablePadding>\n <CheckCard\n check={check}\n isSelected={selectedId === check.id}\n onClick={onCheckSelect}\n onApprovalChange={onApprovalChange}\n disableApproval={disableApproval}\n disabledApprovalTooltip={disabledApprovalTooltip}\n />\n </ListItem>\n ))}\n </List>\n </Box>\n );\n}\n\nexport const CheckList = memo(CheckListComponent);\nCheckList.displayName = \"CheckList\";\n","\"use client\";\n\nimport {\n Background,\n Controls,\n type Edge,\n MiniMap,\n type Node,\n ReactFlow,\n useEdgesState,\n useNodesState,\n} from \"@xyflow/react\";\nimport \"@xyflow/react/dist/style.css\";\nimport Box from \"@mui/material/Box\";\nimport { useCallback } from \"react\";\n\nimport { LineageColumnNode } from \"./columns\";\nimport { LineageEdge, type LineageEdgeData } from \"./edges\";\nimport { LineageNode, type LineageNodeData } from \"./nodes\";\n\nexport interface LineageCanvasProps {\n /** Nodes to display */\n nodes: Node<LineageNodeData>[];\n /** Edges connecting nodes */\n edges: Edge<LineageEdgeData>[];\n /** Callback when node selection changes */\n onNodeSelect?: (nodeId: string | null) => void;\n /** Callback when node is double-clicked */\n onNodeDoubleClick?: (nodeId: string) => void;\n /** Whether to show minimap */\n showMiniMap?: boolean;\n /** Whether to show controls */\n showControls?: boolean;\n /** Whether to show background grid */\n showBackground?: boolean;\n /** Height of the graph container */\n height?: number | string;\n /** Whether the graph is interactive */\n interactive?: boolean;\n}\n\nconst nodeTypes = {\n lineageNode: LineageNode,\n lineageGraphColumnNode: LineageColumnNode,\n};\n\nconst edgeTypes = {\n lineageEdge: LineageEdge,\n};\n\nexport function LineageCanvas({\n nodes: initialNodes,\n edges: initialEdges,\n onNodeSelect,\n onNodeDoubleClick,\n showMiniMap = true,\n showControls = true,\n showBackground = true,\n height = 600,\n interactive = true,\n}: LineageCanvasProps) {\n const [nodes, _setNodes, onNodesChange] = useNodesState(initialNodes);\n const [edges, _setEdges, onEdgesChange] = useEdgesState(initialEdges);\n\n const handleNodeClick = useCallback(\n (_event: React.MouseEvent, node: Node) => {\n onNodeSelect?.(node.id);\n },\n [onNodeSelect],\n );\n\n const handlePaneClick = useCallback(() => {\n onNodeSelect?.(null);\n }, [onNodeSelect]);\n\n const handleNodeDoubleClick = useCallback(\n (_event: React.MouseEvent, node: Node) => {\n onNodeDoubleClick?.(node.id);\n },\n [onNodeDoubleClick],\n );\n\n return (\n <Box sx={{ width: \"100%\", height }}>\n <ReactFlow\n nodes={nodes}\n edges={edges}\n onNodesChange={interactive ? onNodesChange : undefined}\n onEdgesChange={interactive ? onEdgesChange : undefined}\n onNodeClick={handleNodeClick}\n onNodeDoubleClick={handleNodeDoubleClick}\n onPaneClick={handlePaneClick}\n nodeTypes={nodeTypes}\n edgeTypes={edgeTypes}\n fitView\n nodesDraggable={interactive}\n nodesConnectable={false}\n elementsSelectable={interactive}\n >\n {showBackground && <Background />}\n {showControls && <Controls />}\n {showMiniMap && (\n <MiniMap\n nodeColor={(node) => {\n const data = node.data as LineageNodeData;\n switch (data.changeStatus) {\n case \"added\":\n return \"#22c55e\";\n case \"removed\":\n return \"#ef4444\";\n case \"modified\":\n return \"#f59e0b\";\n default:\n return \"#94a3b8\";\n }\n }}\n />\n )}\n </ReactFlow>\n </Box>\n );\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport Typography from \"@mui/material/Typography\";\nimport type { Edge, Node } from \"@xyflow/react\";\nimport { toPng } from \"html-to-image\";\nimport {\n forwardRef,\n type Ref,\n useCallback,\n useImperativeHandle,\n useMemo,\n useRef,\n} from \"react\";\n\nimport { useLineageGraphContext } from \"../../contexts/lineage\";\nimport type {\n LineageGraph,\n LineageGraphEdge,\n LineageGraphNode,\n} from \"../../contexts/lineage/types\";\nimport {\n selectDownstream,\n selectUpstream,\n toReactFlowBasic,\n} from \"../../contexts/lineage/utils\";\nimport type { LineageEdgeData } from \"./edges\";\nimport { LineageCanvas } from \"./LineageCanvas\";\nimport type { LineageNodeData } from \"./nodes\";\n\n/**\n * Props for the LineageView component.\n * Defines options for viewing lineage diff data.\n */\nexport interface LineageViewProps {\n /**\n * Optional lineage graph data. If not provided, uses LineageGraphContext.\n */\n lineageGraph?: LineageGraph;\n\n /**\n * View options for lineage diff visualization\n */\n viewOptions?: {\n view_mode?: \"changed_models\" | \"all\";\n node_ids?: string[];\n select?: string;\n exclude?: string;\n packages?: string[];\n column_level_lineage?: {\n node_id?: string;\n column?: string;\n change_analysis?: boolean;\n };\n };\n\n /**\n * Whether the view allows user interaction\n * @default true\n */\n interactive?: boolean;\n\n /**\n * Optional height for the view\n * @default 600\n */\n height?: number | string;\n\n /**\n * Optional filter function for nodes\n */\n filterNodes?: (key: string, node: LineageGraphNode) => boolean;\n\n /**\n * Callback when a node is selected\n */\n onNodeSelect?: (nodeId: string | null) => void;\n\n /**\n * Callback when a node is double-clicked\n */\n onNodeDoubleClick?: (nodeId: string) => void;\n\n /**\n * Optional dagre instance for layout.\n * If not provided, nodes will be positioned at (0,0).\n * Install @dagrejs/dagre and pass the imported module.\n */\n // biome-ignore lint/suspicious/noExplicitAny: dagre is external dependency\n dagre?: any;\n\n /**\n * Whether to show the minimap\n * @default true\n */\n showMiniMap?: boolean;\n\n /**\n * Whether to show the controls\n * @default true\n */\n showControls?: boolean;\n\n /**\n * Whether to show the background grid\n * @default true\n */\n showBackground?: boolean;\n}\n\n/**\n * Ref interface for LineageView component.\n * Provides methods to interact with the LineageView programmatically.\n */\nexport interface LineageViewRef {\n /**\n * Copies the current lineage view as an image to the clipboard\n */\n copyToClipboard: () => Promise<void>;\n}\n\n/**\n * LineageView Component\n *\n * A high-level component for visualizing data lineage graphs using React Flow.\n * Shows relationships between models and their change status.\n *\n * Can receive data from:\n * 1. LineageGraphContext (wrap with LineageGraphProvider)\n * 2. Direct prop (pass lineageGraph prop)\n *\n * For auto-layout, provide a dagre instance:\n *\n * @example Using with context\n * ```tsx\n * import { LineageGraphProvider, LineageView } from '@datarecce/ui';\n * import dagre from '@dagrejs/dagre';\n *\n * function App() {\n * return (\n * <LineageGraphProvider serverInfo={serverInfo}>\n * <LineageView dagre={dagre} />\n * </LineageGraphProvider>\n * );\n * }\n * ```\n *\n * @example Using with direct data\n * ```tsx\n * import { buildLineageGraph, LineageView } from '@datarecce/ui';\n * import dagre from '@dagrejs/dagre';\n *\n * function App({ serverInfo }) {\n * const lineageGraph = buildLineageGraph(\n * serverInfo.lineage.base,\n * serverInfo.lineage.current,\n * serverInfo.lineage.diff\n * );\n *\n * return <LineageView lineageGraph={lineageGraph} dagre={dagre} />;\n * }\n * ```\n */\nexport const LineageView = forwardRef<LineageViewRef, LineageViewProps>(\n function LineageView(props: LineageViewProps, ref: Ref<LineageViewRef>) {\n const {\n lineageGraph: propLineageGraph,\n viewOptions,\n interactive = true,\n height = 600,\n filterNodes,\n onNodeSelect,\n onNodeDoubleClick,\n dagre,\n showMiniMap = true,\n showControls = true,\n showBackground = true,\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Get lineage graph from context or props\n const contextValue = useLineageGraphContext();\n const lineageGraph = propLineageGraph ?? contextValue.lineageGraph;\n const isLoading = !propLineageGraph && contextValue.isLoading;\n const error = !propLineageGraph ? contextValue.error : undefined;\n\n // Apply filtering and convert to React Flow format\n const { nodes, edges } = useMemo((): {\n nodes: Node<LineageNodeData>[];\n edges: Edge<LineageEdgeData>[];\n } => {\n if (!lineageGraph) {\n return { nodes: [], edges: [] };\n }\n\n // Determine which nodes to include\n let selectedNodeIds: string[] | undefined;\n\n if (viewOptions?.node_ids && viewOptions.node_ids.length > 0) {\n // Explicit node selection\n selectedNodeIds = viewOptions.node_ids;\n } else if (viewOptions?.view_mode === \"changed_models\") {\n // Only changed models\n selectedNodeIds = lineageGraph.modifiedSet;\n }\n\n // Apply select/exclude patterns (simplified - expand upstream/downstream)\n if (viewOptions?.select && selectedNodeIds) {\n // Simple pattern: +upstream, +downstream\n if (viewOptions.select.includes(\"+\")) {\n const upstream = selectUpstream(lineageGraph, selectedNodeIds);\n const downstream = selectDownstream(lineageGraph, selectedNodeIds);\n selectedNodeIds = [...new Set([...upstream, ...downstream])];\n }\n }\n\n // Apply package filter\n if (viewOptions?.packages && viewOptions.packages.length > 0) {\n const packageSet = new Set(viewOptions.packages);\n const allNodeIds = selectedNodeIds ?? Object.keys(lineageGraph.nodes);\n selectedNodeIds = allNodeIds.filter((id) => {\n const node = lineageGraph.nodes[id];\n return (\n node?.data.packageName && packageSet.has(node.data.packageName)\n );\n });\n }\n\n // Apply custom filter\n if (filterNodes) {\n const allNodeIds = selectedNodeIds ?? Object.keys(lineageGraph.nodes);\n selectedNodeIds = allNodeIds.filter((id) => {\n const node = lineageGraph.nodes[id];\n return node ? filterNodes(id, node) : false;\n });\n }\n\n // Convert to React Flow format (internal types)\n const [rfNodes, rfEdges] = toReactFlowBasic(\n lineageGraph,\n selectedNodeIds,\n );\n\n // Apply dagre layout if provided\n if (dagre && rfNodes.length > 0) {\n applyDagreLayout(dagre, rfNodes, rfEdges);\n }\n\n // Convert to presentation types (LineageNodeData / LineageEdgeData)\n const presentationNodes: Node<LineageNodeData>[] = rfNodes.map(\n (node) => ({\n id: node.id,\n position: node.position,\n type: \"lineageNode\",\n data: {\n label: node.data.name,\n nodeType: node.data.resourceType,\n changeStatus: node.data.changeStatus,\n resourceType: node.data.resourceType,\n packageName: node.data.packageName,\n },\n }),\n );\n\n const presentationEdges: Edge<LineageEdgeData>[] = rfEdges.map(\n (edge) => ({\n id: edge.id,\n source: edge.source,\n target: edge.target,\n type: \"lineageEdge\",\n data: {\n changeStatus: edge.data?.changeStatus,\n },\n }),\n );\n\n return {\n nodes: presentationNodes,\n edges: presentationEdges,\n };\n }, [lineageGraph, viewOptions, filterNodes, dagre]);\n\n // Copy to clipboard implementation\n const copyToClipboard = useCallback(async () => {\n if (!containerRef.current) {\n return;\n }\n\n try {\n const dataUrl = await toPng(containerRef.current, {\n backgroundColor: \"#ffffff\",\n pixelRatio: 2,\n });\n\n const response = await fetch(dataUrl);\n const blob = await response.blob();\n\n await navigator.clipboard.write([\n new ClipboardItem({\n [blob.type]: blob,\n }),\n ]);\n } catch (err) {\n console.error(\"Failed to copy to clipboard:\", err);\n throw err;\n }\n }, []);\n\n // Expose ref methods\n useImperativeHandle(\n ref,\n () => ({\n copyToClipboard,\n }),\n [copyToClipboard],\n );\n\n // Loading state\n if (isLoading) {\n return (\n <Box\n sx={{\n width: \"100%\",\n height,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <CircularProgress />\n </Box>\n );\n }\n\n // Error state\n if (error) {\n return (\n <Box\n sx={{\n width: \"100%\",\n height,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Typography color=\"error\">{error}</Typography>\n </Box>\n );\n }\n\n // No data state\n if (!lineageGraph) {\n return (\n <Box\n sx={{\n width: \"100%\",\n height,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Typography color=\"text.secondary\">\n No lineage data available. Provide data via LineageGraphProvider or\n lineageGraph prop.\n </Typography>\n </Box>\n );\n }\n\n // Empty after filtering\n if (nodes.length === 0) {\n return (\n <Box\n sx={{\n width: \"100%\",\n height,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Typography color=\"text.secondary\">\n No nodes match the current filter criteria.\n </Typography>\n </Box>\n );\n }\n\n return (\n <Box ref={containerRef} sx={{ width: \"100%\", height }}>\n <LineageCanvas\n nodes={nodes}\n edges={edges}\n onNodeSelect={onNodeSelect}\n onNodeDoubleClick={onNodeDoubleClick}\n showMiniMap={showMiniMap}\n showControls={showControls}\n showBackground={showBackground}\n height=\"100%\"\n interactive={interactive}\n />\n </Box>\n );\n },\n);\n\n/**\n * Apply dagre layout to nodes and edges (internal helper)\n */\nfunction applyDagreLayout(\n // biome-ignore lint/suspicious/noExplicitAny: dagre is external dependency\n dagre: any,\n nodes: LineageGraphNode[],\n edges: LineageGraphEdge[],\n direction = \"LR\",\n): void {\n const dagreGraph = new dagre.graphlib.Graph();\n dagreGraph.setDefaultEdgeLabel(() => ({}));\n dagreGraph.setGraph({ rankdir: direction, ranksep: 50, nodesep: 30 });\n\n for (const node of nodes) {\n const width = node.style?.width ? Number(node.style.width) : 300;\n const height = node.style?.height ? Number(node.style.height) : 60;\n dagreGraph.setNode(node.id, { width, height });\n }\n\n for (const edge of edges) {\n dagreGraph.setEdge(edge.source, edge.target);\n }\n\n dagre.layout(dagreGraph);\n\n for (const node of nodes) {\n const nodeWidth = node.style?.width ? Number(node.style.width) : 300;\n const nodeHeight = node.style?.height ? Number(node.style.height) : 60;\n const nodeWithPosition = dagreGraph.node(node.id);\n\n if (nodeWithPosition) {\n node.position = {\n x: nodeWithPosition.x - nodeWidth / 2,\n y: nodeWithPosition.y - nodeHeight / 2,\n };\n }\n }\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport { ReactFlowProvider } from \"@xyflow/react\";\nimport { forwardRef, type Ref } from \"react\";\n\nimport { LineageView, type LineageViewRef } from \"../lineage/LineageView\";\n\n/**\n * View options for lineage diff checks\n */\nexport interface LineageDiffViewOptions {\n view_mode?: \"changed_models\" | \"all\";\n node_ids?: string[];\n select?: string;\n exclude?: string;\n packages?: string[];\n column_level_lineage?: {\n node_id?: string;\n column?: string;\n change_analysis?: boolean;\n };\n}\n\n/**\n * Props for the LineageDiffView component\n */\nexport interface LineageDiffViewProps {\n /**\n * Parameters from the check, merged with view options\n */\n params?: Record<string, unknown>;\n\n /**\n * View options for the lineage display\n */\n viewOptions?: LineageDiffViewOptions;\n\n /**\n * Whether the view is interactive (allows user input)\n * @default false\n */\n interactive?: boolean;\n\n /**\n * Optional height for the view\n */\n height?: number | string;\n\n /**\n * Optional dagre instance for layout\n */\n // biome-ignore lint/suspicious/noExplicitAny: dagre is external dependency\n dagre?: any;\n}\n\n/**\n * LineageDiffView Component\n *\n * A presentation component for displaying lineage diff results within a check.\n * Wraps the LineageView component with ReactFlowProvider and handles\n * merging of check params with view options.\n *\n * @example Basic usage\n * ```tsx\n * import { LineageDiffView } from '@datarecce/ui/primitives';\n * import dagre from '@dagrejs/dagre';\n *\n * function CheckLineageResult({ check }) {\n * return (\n * <LineageDiffView\n * params={check.params}\n * viewOptions={check.view_options}\n * dagre={dagre}\n * />\n * );\n * }\n * ```\n *\n * @example With ref for clipboard\n * ```tsx\n * const lineageRef = useRef<LineageViewRef>(null);\n *\n * const handleCopy = () => {\n * lineageRef.current?.copyToClipboard();\n * };\n *\n * <LineageDiffView\n * ref={lineageRef}\n * params={check.params}\n * viewOptions={check.view_options}\n * />\n * ```\n */\nfunction LineageDiffViewComponent(\n {\n params,\n viewOptions,\n interactive = false,\n height,\n dagre,\n }: LineageDiffViewProps,\n ref: Ref<LineageViewRef>,\n) {\n // Merge params with view options - params take precedence\n const mergedViewOptions = {\n ...viewOptions,\n ...params,\n };\n\n return (\n <Box\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n height: height ?? \"100%\",\n }}\n >\n <ReactFlowProvider>\n <LineageView\n viewOptions={mergedViewOptions}\n interactive={interactive}\n ref={ref}\n dagre={dagre}\n />\n </ReactFlowProvider>\n </Box>\n );\n}\n\nexport const LineageDiffView = forwardRef<LineageViewRef, LineageDiffViewProps>(\n LineageDiffViewComponent,\n);\nLineageDiffView.displayName = \"LineageDiffView\";\n\n// Re-export LineageViewRef for convenience\nexport type { LineageViewRef };\n","\"use client\";\n\nimport { PostgreSQL, sql } from \"@codemirror/lang-sql\";\nimport { yaml } from \"@codemirror/lang-yaml\";\nimport { Prec } from \"@codemirror/state\";\nimport { EditorView, keymap } from \"@codemirror/view\";\nimport { githubDark, githubLight } from \"@uiw/codemirror-theme-github\";\nimport CodeMirror from \"@uiw/react-codemirror\";\nimport { useMemo } from \"react\";\n\n/**\n * Supported languages for the code editor\n */\nexport type CodeEditorLanguage = \"sql\" | \"yaml\" | \"text\";\n\n/**\n * Theme options for the code editor\n */\nexport type CodeEditorTheme = \"light\" | \"dark\";\n\n/**\n * Props for the CodeEditor component\n */\nexport interface CodeEditorProps {\n /** The code content to display */\n value: string;\n /** Callback when content changes */\n onChange?: (value: string) => void;\n /** Language for syntax highlighting */\n language?: CodeEditorLanguage;\n /** Whether editor is read-only */\n readOnly?: boolean;\n /** Show line numbers */\n lineNumbers?: boolean;\n /** Enable word wrap */\n wordWrap?: boolean;\n /** Font size in pixels */\n fontSize?: number;\n /** Editor height */\n height?: string;\n /** Optional CSS class */\n className?: string;\n /** Theme mode */\n theme?: CodeEditorTheme;\n /** Custom keyboard shortcuts */\n keyBindings?: Array<{ key: string; run: () => boolean }>;\n}\n\n/**\n * Get language extension for CodeMirror\n */\nfunction getLanguageExtension(language: CodeEditorLanguage) {\n switch (language) {\n case \"sql\":\n return sql({ dialect: PostgreSQL });\n case \"yaml\":\n return yaml();\n default:\n return null;\n }\n}\n\n/**\n * CodeEditor Component\n *\n * A code editor component using CodeMirror with React integration.\n * Supports SQL and YAML syntax highlighting with customizable theming.\n *\n * @example Basic usage\n * ```tsx\n * import { CodeEditor } from '@datarecce/ui/primitives';\n *\n * function SqlEditor({ sql, onSqlChange }) {\n * return (\n * <CodeEditor\n * value={sql}\n * onChange={onSqlChange}\n * language=\"sql\"\n * />\n * );\n * }\n * ```\n *\n * @example Read-only with dark theme\n * ```tsx\n * <CodeEditor\n * value={yamlContent}\n * language=\"yaml\"\n * readOnly\n * theme=\"dark\"\n * height=\"300px\"\n * />\n * ```\n *\n * @example With custom key bindings\n * ```tsx\n * const keyBindings = [\n * { key: \"Mod-Enter\", run: () => { handleSubmit(); return true; } }\n * ];\n *\n * <CodeEditor\n * value={sql}\n * onChange={setSql}\n * language=\"sql\"\n * keyBindings={keyBindings}\n * />\n * ```\n */\nexport function CodeEditor({\n value,\n onChange,\n language = \"sql\",\n readOnly = false,\n lineNumbers = true,\n wordWrap = true,\n fontSize = 14,\n height = \"100%\",\n className = \"\",\n theme = \"light\",\n keyBindings = [],\n}: CodeEditorProps) {\n const extensions = useMemo(() => {\n const exts = [\n EditorView.theme({\n \"&\": { fontSize: `${fontSize}px` },\n \".cm-content\": {\n fontFamily: \"'JetBrains Mono', 'Fira Code', monospace\",\n },\n \".cm-gutters\": {\n fontFamily: \"'JetBrains Mono', 'Fira Code', monospace\",\n },\n }),\n ];\n\n // Add language extension if not plain text\n const langExt = getLanguageExtension(language);\n if (langExt) {\n exts.push(langExt);\n }\n\n if (wordWrap) {\n exts.push(EditorView.lineWrapping);\n }\n\n // Use Prec.highest to ensure custom keybindings take precedence\n // over defaultKeymap bindings (e.g., Mod-Enter -> insertBlankLine)\n if (keyBindings.length > 0) {\n exts.push(Prec.highest(keymap.of(keyBindings)));\n }\n\n return exts;\n }, [language, fontSize, wordWrap, keyBindings]);\n\n const themeExtension = useMemo(() => {\n return theme === \"dark\" ? githubDark : githubLight;\n }, [theme]);\n\n const handleChange = (val: string) => {\n if (onChange) {\n onChange(val);\n }\n };\n\n return (\n <CodeMirror\n value={value}\n onChange={handleChange}\n extensions={extensions}\n readOnly={readOnly}\n basicSetup={{\n lineNumbers,\n foldGutter: true,\n highlightActiveLineGutter: !readOnly,\n highlightActiveLine: !readOnly,\n tabSize: 2,\n }}\n height={height}\n className={`${className} no-track-pii-safe`}\n theme={themeExtension}\n />\n );\n}\n\nexport default CodeEditor;\n","\"use client\";\n\nimport { memo } from \"react\";\nimport YAML from \"yaml\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\nimport { CodeEditor } from \"../editor/CodeEditor\";\n\n/**\n * Props for generating a preset check template\n */\nexport interface GenerateCheckTemplateOptions {\n /** Check name */\n name: string;\n /** Check description */\n description: string;\n /** Check type (e.g., \"row_count_diff\", \"schema_diff\") */\n type: string;\n /** Check parameters */\n params: Record<string, unknown>;\n /** View options (optional) */\n viewOptions?: Record<string, unknown>;\n}\n\n/**\n * Generates a YAML template for a preset check configuration.\n * This can be used to export checks to recce.yml.\n *\n * @example\n * ```ts\n * const template = generateCheckTemplate({\n * name: \"Schema Check\",\n * description: \"Check for schema changes\",\n * type: \"schema_diff\",\n * params: { select: \"state:modified\" },\n * });\n * // Returns:\n * // checks:\n * // - name: Schema Check\n * // description: Check for schema changes\n * // type: schema_diff\n * // params:\n * // select: state:modified\n * ```\n */\nexport function generateCheckTemplate({\n name,\n description,\n type,\n params,\n viewOptions,\n}: GenerateCheckTemplateOptions): string {\n const check: Record<string, unknown> = { name, description, type, params };\n if (viewOptions) {\n check.view_options = viewOptions;\n }\n return YAML.stringify({\n checks: [check],\n });\n}\n\n/**\n * Props for the PresetCheckTemplateView component\n */\nexport interface PresetCheckTemplateViewProps {\n /** The YAML template string to display */\n yamlTemplate: string;\n /** Optional height for the editor */\n height?: string;\n}\n\n/**\n * PresetCheckTemplateView Component\n *\n * A presentation component for displaying a preset check YAML template\n * in a read-only code editor. Used in modals to show users how to\n * add checks to their recce.yml file.\n *\n * @example Basic usage\n * ```tsx\n * import { PresetCheckTemplateView, generateCheckTemplate } from '@datarecce/ui/primitives';\n *\n * function CheckTemplateDialog({ check }) {\n * const template = generateCheckTemplate({\n * name: check.name,\n * description: check.description,\n * type: check.type,\n * params: check.params,\n * });\n *\n * return (\n * <Dialog open>\n * <DialogContent>\n * <PresetCheckTemplateView yamlTemplate={template} />\n * </DialogContent>\n * </Dialog>\n * );\n * }\n * ```\n */\nfunction PresetCheckTemplateViewComponent({\n yamlTemplate,\n height = \"300px\",\n}: PresetCheckTemplateViewProps) {\n const isDark = useIsDark();\n return (\n <CodeEditor\n value={yamlTemplate}\n language=\"yaml\"\n readOnly={true}\n lineNumbers={false}\n wordWrap={true}\n fontSize={14}\n theme={isDark ? \"dark\" : \"light\"}\n height={height}\n />\n );\n}\n\nexport const PresetCheckTemplateView = memo(PresetCheckTemplateViewComponent);\nPresetCheckTemplateView.displayName = \"PresetCheckTemplateView\";\n","import axios, { type AxiosInstance } from \"axios\";\nimport { PUBLIC_API_URL } from \"../lib/const\";\nimport { useApiConfigOptional } from \"../providers\";\n\n/**\n * API configuration adapter for OSS and cloud hosts.\n *\n * @remarks\n * Fallback chain:\n * 1. RecceProvider (via useApiConfigOptional)\n * 2. Default config using PUBLIC_API_URL\n */\nexport interface ApiConfigContextType {\n /**\n * API endpoint prefix to replace `/api` in all requests.\n * For OSS: \"\" (empty string, uses default /api/* paths)\n * For Cloud: \"/api/v2/sessions/<session_id>\" (replaces /api with this)\n */\n apiPrefix: string;\n\n /**\n * Optional authentication token for API requests.\n * When provided, adds \"Authorization: Bearer <token>\" header.\n */\n authToken?: string;\n\n /**\n * Optional base URL override.\n * When provided, overrides the PUBLIC_API_URL for this context.\n */\n baseUrl?: string;\n /**\n * Pre-configured axios instance with interceptors for\n * API prefix replacement and auth token injection.\n */\n apiClient: AxiosInstance;\n}\n\n// Default config used when ApiConfigProvider is not present (OSS mode)\nconst defaultApiConfigContext: ApiConfigContextType = {\n apiPrefix: \"\",\n authToken: undefined,\n baseUrl: undefined,\n apiClient: axios.create({ baseURL: PUBLIC_API_URL }),\n};\n\n/**\n * Access the API configuration and configured axios client.\n *\n * @remarks\n * Priority order:\n * 1. RecceProvider (via useApiConfigOptional)\n * 2. Default config (for backward compatibility)\n */\nexport function useApiConfig(): ApiConfigContextType {\n // Call both hooks unconditionally (React hooks rules)\n const datarecceContext = useApiConfigOptional();\n\n // Priority: @datarecce/ui provider > defaults\n return datarecceContext ?? defaultApiConfigContext;\n}\n","/**\n * useAvatar - Hook for fetching user avatars with proper fallback handling.\n *\n * Solves the issue where email-only login users (non-GitHub) were showing\n * incorrect GitHub avatars because their Recce Cloud user_id was being\n * passed to the GitHub API.\n *\n * Logic:\n * 1. Fetch current user data to get login_type\n * 2. If actor's user_id matches current user AND login_type is \"github\":\n * - Fetch and return GitHub avatar URL\n * 3. Otherwise:\n * - Return null (component should show initials fallback)\n */\n\nimport { useQuery } from \"@tanstack/react-query\";\nimport { cacheKeys } from \"../api\";\nimport { fetchGitHubAvatar, fetchUser } from \"../lib/api/user\";\nimport { useApiConfig } from \"./useApiConfig\";\n\nexport interface UseAvatarOptions {\n /** The user ID from the event actor */\n userId: string | number | null | undefined;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseAvatarResult {\n /** The avatar URL if available, null otherwise */\n avatarUrl: string | null;\n /** Whether the avatar is loading */\n isLoading: boolean;\n /** Whether this user is a GitHub user */\n isGitHubUser: boolean;\n}\n\n/**\n * Hook to fetch a user's avatar with proper fallback handling.\n *\n * Only fetches GitHub avatars for users who authenticated via GitHub.\n * For email-only users, returns null so the component can show initials.\n *\n * @example\n * ```tsx\n * function UserAvatar({ userId, displayName }: Props) {\n * const { avatarUrl } = useAvatar({ userId });\n *\n * return (\n * <MuiAvatar src={avatarUrl || undefined}>\n * {displayName.charAt(0).toUpperCase()}\n * </MuiAvatar>\n * );\n * }\n * ```\n */\nexport function useAvatar({\n userId,\n enabled = true,\n}: UseAvatarOptions): UseAvatarResult {\n const { apiClient } = useApiConfig();\n const userIdString = userId?.toString();\n\n // Fetch current user to get login_type\n const { data: currentUser, isLoading: isUserLoading } = useQuery({\n queryKey: cacheKeys.user(),\n queryFn: () => fetchUser(apiClient),\n enabled: enabled && !!userIdString,\n retry: false,\n staleTime: 5 * 60 * 1000, // Cache for 5 minutes\n });\n\n // Determine if this is a GitHub user\n // Only fetch GitHub avatar if:\n // 1. The actor's user_id matches the current user's id\n // 2. The current user authenticated via GitHub\n const isCurrentUser = Boolean(\n currentUser && userIdString && currentUser.id === userIdString,\n );\n const isGitHubUser = isCurrentUser && currentUser?.login_type === \"github\";\n const shouldFetchAvatar = Boolean(enabled && isGitHubUser && userIdString);\n\n // Fetch GitHub avatar only for GitHub users\n const { data: avatarUrl, isLoading: isAvatarLoading } = useQuery({\n queryKey: [\"github-avatar\", userIdString],\n queryFn: () =>\n userIdString ? fetchGitHubAvatar(userIdString) : Promise.resolve(null),\n enabled: shouldFetchAvatar,\n retry: false,\n staleTime: 5 * 60 * 1000, // Cache for 5 minutes\n });\n\n return {\n avatarUrl: typeof avatarUrl === \"string\" ? avatarUrl : null,\n isLoading: isUserLoading || (shouldFetchAvatar && isAvatarLoading),\n isGitHubUser: Boolean(isGitHubUser),\n };\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport { memo, useCallback, useState } from \"react\";\nimport { useIsDark } from \"../../../hooks/useIsDark\";\n\n/**\n * Props for the CommentInput component\n */\nexport interface CommentInputProps {\n /** Callback when comment is submitted */\n onSubmit: (content: string) => void;\n /** Whether submission is in progress */\n isSubmitting?: boolean;\n /** Placeholder text for the input */\n placeholder?: string;\n /** Button text (defaults to \"Comment\") */\n submitLabel?: string;\n /** Submitting button text (defaults to \"Submitting...\") */\n submittingLabel?: string;\n /** Optional CSS class name */\n className?: string;\n}\n\n/**\n * CommentInput Component\n *\n * A pure presentation component for adding comments to a timeline.\n * Supports Cmd+Enter (Mac) or Ctrl+Enter (Windows) keyboard shortcut to submit.\n *\n * @example Basic usage\n * ```tsx\n * import { CommentInput } from '@datarecce/ui/primitives';\n *\n * function CheckTimeline({ checkId }) {\n * const [isSubmitting, setIsSubmitting] = useState(false);\n *\n * const handleSubmit = async (content: string) => {\n * setIsSubmitting(true);\n * await addComment(checkId, content);\n * setIsSubmitting(false);\n * };\n *\n * return (\n * <CommentInput\n * onSubmit={handleSubmit}\n * isSubmitting={isSubmitting}\n * />\n * );\n * }\n * ```\n *\n * @example Custom labels\n * ```tsx\n * <CommentInput\n * onSubmit={handleSubmit}\n * placeholder=\"Leave feedback...\"\n * submitLabel=\"Send\"\n * submittingLabel=\"Sending...\"\n * />\n * ```\n */\nfunction CommentInputComponent({\n onSubmit,\n isSubmitting = false,\n placeholder = \"Add a comment...\",\n submitLabel = \"Comment\",\n submittingLabel = \"Submitting...\",\n className,\n}: CommentInputProps) {\n const isDark = useIsDark();\n const [content, setContent] = useState(\"\");\n\n const handleSubmit = useCallback(() => {\n const trimmed = content.trim();\n if (trimmed) {\n onSubmit(trimmed);\n setContent(\"\");\n }\n }, [content, onSubmit]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n // Submit on Cmd+Enter or Ctrl+Enter\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSubmit();\n }\n },\n [handleSubmit],\n );\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n setContent(e.target.value);\n },\n [],\n );\n\n return (\n <Box className={className}>\n <TextField\n value={content}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n size=\"small\"\n multiline\n fullWidth\n minRows={3}\n disabled={isSubmitting}\n sx={{\n \"& .MuiOutlinedInput-root\": {\n bgcolor: \"background.paper\",\n \"&:hover .MuiOutlinedInput-notchedOutline\": {\n borderColor: isDark ? \"grey.500\" : \"grey.400\",\n },\n \"&.Mui-focused .MuiOutlinedInput-notchedOutline\": {\n borderColor: \"iochmara.400\",\n boxShadow: \"0 0 0 1px #4299E1\",\n },\n },\n \"& .MuiOutlinedInput-notchedOutline\": {\n borderColor: isDark ? \"grey.600\" : undefined,\n },\n }}\n />\n <Stack direction=\"row\" justifyContent=\"flex-end\" sx={{ mt: 2 }}>\n <Button\n size=\"small\"\n color=\"iochmara\"\n variant=\"contained\"\n onClick={handleSubmit}\n disabled={!content.trim() || isSubmitting}\n >\n {isSubmitting ? submittingLabel : submitLabel}\n </Button>\n </Stack>\n </Box>\n );\n}\n\nexport const CommentInput = memo(CommentInputComponent);\nCommentInput.displayName = \"CommentInput\";\n","\"use client\";\n\nimport MuiAvatar from \"@mui/material/Avatar\";\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Popover from \"@mui/material/Popover\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport MuiTooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { type MouseEvent, memo, useCallback, useState } from \"react\";\nimport {\n PiBookmarkSimple,\n PiChatText,\n PiCheckCircle,\n PiCircle,\n PiNotePencil,\n PiPencilSimple,\n PiPlusCircle,\n PiTrashSimple,\n} from \"react-icons/pi\";\nimport { useIsDark } from \"../../../hooks/useIsDark\";\n\n/**\n * Actor information for timeline events\n */\nexport interface TimelineActor {\n /** User ID */\n user_id?: string | number;\n /** Full name of the actor */\n fullname?: string;\n /** Login/username of the actor */\n login?: string;\n /** Avatar URL (props-driven, consumer provides) */\n avatarUrl?: string;\n}\n\n/**\n * Event types supported by the timeline\n */\nexport type TimelineEventType =\n | \"check_created\"\n | \"comment\"\n | \"approval_change\"\n | \"description_change\"\n | \"name_change\"\n | \"preset_applied\";\n\n/**\n * Timeline event data structure\n */\nexport interface TimelineEventData {\n /** Unique event ID */\n id: string;\n /** Type of event */\n event_type: TimelineEventType;\n /** Actor who performed the event */\n actor: TimelineActor;\n /** Event creation timestamp (ISO string) */\n created_at: string;\n /** Event content (for comments) */\n content?: string;\n /** New value (for change events) */\n new_value?: string;\n /** Whether the event was edited */\n is_edited?: boolean;\n /** Whether the event was deleted */\n is_deleted?: boolean;\n}\n\n/**\n * Icon type mapping for events\n */\ntype EventIconType =\n | \"create\"\n | \"comment\"\n | \"approve\"\n | \"unapprove\"\n | \"edit\"\n | \"preset\";\n\n/**\n * Get the icon type for an event\n */\nfunction getEventIconType(event: TimelineEventData): EventIconType {\n switch (event.event_type) {\n case \"check_created\":\n return \"create\";\n case \"comment\":\n return \"comment\";\n case \"approval_change\":\n return event.new_value === \"true\" ? \"approve\" : \"unapprove\";\n case \"description_change\":\n case \"name_change\":\n return \"edit\";\n case \"preset_applied\":\n return \"preset\";\n default:\n return \"edit\";\n }\n}\n\n/**\n * Props for the TimelineEvent component\n */\nexport interface TimelineEventProps {\n /** Event data to render */\n event: TimelineEventData;\n /** Current user ID (to show edit/delete for own comments) */\n currentUserId?: string;\n /** Callback when editing a comment */\n onEdit?: (eventId: string, content: string) => Promise<void>;\n /** Callback when deleting a comment */\n onDelete?: (eventId: string) => Promise<void>;\n /** Optional markdown renderer component */\n markdownRenderer?: React.ComponentType<{ content: string }>;\n /** Optional CSS class name */\n className?: string;\n}\n\nfunction EventIcon({ event }: { event: TimelineEventData }) {\n const iconType = getEventIconType(event);\n\n const iconMap = {\n create: PiPlusCircle,\n comment: PiChatText,\n approve: PiCheckCircle,\n unapprove: PiCircle,\n edit: PiNotePencil,\n preset: PiBookmarkSimple,\n };\n\n const colorMap: Record<string, string> = {\n create: \"primary.main\",\n comment: \"grey.500\",\n approve: \"success.main\",\n unapprove: \"grey.400\",\n edit: \"warning.main\",\n preset: \"secondary.main\",\n };\n\n const IconComponent = iconMap[iconType];\n const color = colorMap[iconType];\n\n return <Box component={IconComponent} sx={{ color, fontSize: 16 }} />;\n}\n\nfunction UserAvatar({ actor }: { actor: TimelineActor }) {\n const displayName = actor.fullname || actor.login || \"User\";\n const initials = displayName.charAt(0).toUpperCase();\n\n return (\n <MuiAvatar\n src={actor.avatarUrl || undefined}\n sx={{ width: 24, height: 24, fontSize: \"0.75rem\" }}\n >\n {initials}\n </MuiAvatar>\n );\n}\n\nfunction StateChangeEventComponent({ event }: { event: TimelineEventData }) {\n const { actor } = event;\n const actorName = actor.fullname || actor.login || \"Someone\";\n const relativeTime = formatDistanceToNow(new Date(event.created_at), {\n addSuffix: true,\n });\n\n let message = \"\";\n switch (event.event_type) {\n case \"check_created\":\n message = \"created this check\";\n break;\n case \"approval_change\":\n message =\n event.new_value === \"true\"\n ? \"approved this check\"\n : \"unapproved this check\";\n break;\n case \"description_change\":\n message = \"updated the description\";\n break;\n case \"name_change\":\n message = \"renamed this check\";\n break;\n case \"preset_applied\":\n message = \"applied a preset\";\n break;\n default:\n message = \"made a change\";\n }\n\n return (\n <Box sx={{ display: \"flex\", gap: 1, alignItems: \"flex-start\", py: 1 }}>\n <Box sx={{ pt: \"2px\" }}>\n <EventIcon event={event} />\n </Box>\n <Box sx={{ flex: 1 }}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n flexWrap=\"wrap\"\n alignItems=\"center\"\n >\n <UserAvatar actor={actor} />\n <Typography variant=\"body2\" fontWeight=\"500\">\n {actorName}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {message}\n </Typography>\n <Typography variant=\"caption\" color=\"text.disabled\">\n {relativeTime}\n </Typography>\n </Stack>\n </Box>\n </Box>\n );\n}\n\nconst StateChangeEvent = memo(StateChangeEventComponent);\nStateChangeEvent.displayName = \"StateChangeEvent\";\n\nfunction CommentEventComponent({\n event,\n currentUserId,\n onEdit,\n onDelete,\n markdownRenderer: MarkdownRenderer,\n}: {\n event: TimelineEventData;\n currentUserId?: string;\n onEdit?: (eventId: string, content: string) => Promise<void>;\n onDelete?: (eventId: string) => Promise<void>;\n markdownRenderer?: React.ComponentType<{ content: string }>;\n}) {\n const isDark = useIsDark();\n const [isEditing, setIsEditing] = useState(false);\n const [editContent, setEditContent] = useState(event.content || \"\");\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [deleteAnchorEl, setDeleteAnchorEl] = useState<HTMLElement | null>(\n null,\n );\n const isDeletePopoverOpen = Boolean(deleteAnchorEl);\n\n const { actor } = event;\n const actorName = actor.fullname || actor.login || \"Someone\";\n const relativeTime = formatDistanceToNow(new Date(event.created_at), {\n addSuffix: true,\n });\n const isAuthor =\n currentUserId && String(actor.user_id) === String(currentUserId);\n\n const handleStartEdit = useCallback(() => {\n setEditContent(event.content || \"\");\n setIsEditing(true);\n }, [event.content]);\n\n const handleCancelEdit = useCallback(() => {\n setEditContent(event.content || \"\");\n setIsEditing(false);\n }, [event.content]);\n\n const handleSaveEdit = useCallback(async () => {\n const trimmed = editContent.trim();\n if (!trimmed || trimmed === event.content) {\n handleCancelEdit();\n return;\n }\n\n if (onEdit) {\n setIsSubmitting(true);\n try {\n await onEdit(event.id, trimmed);\n setIsEditing(false);\n } finally {\n setIsSubmitting(false);\n }\n }\n }, [editContent, event.content, event.id, onEdit, handleCancelEdit]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n handleCancelEdit();\n } else if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSaveEdit();\n }\n },\n [handleCancelEdit, handleSaveEdit],\n );\n\n const handleDeleteClick = useCallback(\n (clickEvent: MouseEvent<HTMLButtonElement>) => {\n setDeleteAnchorEl(clickEvent.currentTarget);\n },\n [],\n );\n\n const handleDeleteClose = useCallback(() => {\n setDeleteAnchorEl(null);\n }, []);\n\n const handleDelete = useCallback(async () => {\n if (onDelete) {\n setIsDeleting(true);\n try {\n await onDelete(event.id);\n handleDeleteClose();\n } finally {\n setIsDeleting(false);\n }\n }\n }, [onDelete, event.id, handleDeleteClose]);\n\n const handleEditChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n setEditContent(e.target.value);\n },\n [],\n );\n\n if (event.is_deleted) {\n return (\n <Box sx={{ display: \"flex\", gap: 1, alignItems: \"center\", py: 1 }}>\n <Box sx={{ pt: \"2px\", display: \"flex\", alignItems: \"center\" }}>\n <EventIcon event={event} />\n </Box>\n <Box sx={{ display: \"flex\", flex: 1, alignItems: \"center\" }}>\n <Typography variant=\"body2\" color=\"text.disabled\" fontStyle=\"italic\">\n Comment deleted\n </Typography>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box sx={{ display: \"flex\", gap: 1, alignItems: \"flex-start\", py: 1 }}>\n <Box sx={{ pt: \"2px\" }}>\n <EventIcon event={event} />\n </Box>\n <Box sx={{ flex: 1 }}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ mb: 0.5 }}\n flexWrap=\"wrap\"\n alignItems=\"center\"\n >\n <UserAvatar actor={actor} />\n <Typography variant=\"body2\" fontWeight=\"500\">\n {actorName}\n {isAuthor && (\n <Typography\n component=\"span\"\n variant=\"body2\"\n color=\"text.secondary\"\n >\n {\" \"}\n (Author)\n </Typography>\n )}\n </Typography>\n <Typography variant=\"caption\" color=\"text.disabled\">\n {relativeTime}\n </Typography>\n {event.is_edited && (\n <Typography variant=\"caption\" color=\"text.disabled\">\n (edited)\n </Typography>\n )}\n </Stack>\n\n {isEditing ? (\n // Edit mode\n <Box>\n <TextField\n value={editContent}\n onChange={handleEditChange}\n onKeyDown={handleKeyDown}\n size=\"small\"\n multiline\n minRows={3}\n fullWidth\n disabled={isSubmitting}\n autoFocus\n sx={{\n \"& .MuiOutlinedInput-root\": {\n bgcolor: \"background.paper\",\n \"&:focus-within\": {\n borderColor: \"primary.main\",\n },\n },\n }}\n />\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ mt: 1 }}\n justifyContent=\"flex-end\"\n >\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={handleCancelEdit}\n disabled={isSubmitting}\n >\n Cancel\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={handleSaveEdit}\n disabled={!editContent.trim() || isSubmitting}\n >\n {isSubmitting ? \"Saving...\" : \"Save\"}\n </Button>\n </Stack>\n </Box>\n ) : (\n // View mode\n <Box\n sx={{\n bgcolor: isDark ? \"grey.800\" : \"grey.50\",\n borderRadius: 1,\n p: 1,\n border: \"1px solid\",\n borderColor: isDark ? \"grey.700\" : \"grey.200\",\n position: \"relative\",\n \"&:hover .comment-actions\": {\n opacity: 1,\n },\n }}\n >\n {MarkdownRenderer ? (\n <MarkdownRenderer content={event.content || \"\"} />\n ) : (\n <Typography variant=\"body2\" sx={{ whiteSpace: \"pre-wrap\" }}>\n {event.content}\n </Typography>\n )}\n\n {/* Edit/Delete buttons - only visible to author on hover */}\n {isAuthor && (onEdit || onDelete) && (\n <Stack\n className=\"comment-actions\"\n direction=\"row\"\n spacing={0}\n sx={{\n position: \"absolute\",\n top: 4,\n right: 4,\n opacity: 0,\n transition: \"opacity 0.2s\",\n }}\n >\n {onEdit && (\n <MuiTooltip title=\"Edit comment\">\n <IconButton\n aria-label=\"Edit comment\"\n size=\"small\"\n onClick={handleStartEdit}\n >\n <PiPencilSimple />\n </IconButton>\n </MuiTooltip>\n )}\n {onDelete && (\n <>\n <MuiTooltip title=\"Delete comment\">\n <IconButton\n aria-label=\"Delete comment\"\n size=\"small\"\n color=\"error\"\n onClick={handleDeleteClick}\n >\n <PiTrashSimple />\n </IconButton>\n </MuiTooltip>\n <Popover\n open={isDeletePopoverOpen}\n anchorEl={deleteAnchorEl}\n onClose={handleDeleteClose}\n anchorOrigin={{\n vertical: \"bottom\",\n horizontal: \"center\",\n }}\n transformOrigin={{\n vertical: \"top\",\n horizontal: \"center\",\n }}\n >\n <Box sx={{ p: 2 }}>\n <Typography variant=\"body2\" sx={{ mb: 2 }}>\n Delete this comment?\n </Typography>\n <Stack\n direction=\"row\"\n spacing={1}\n justifyContent=\"flex-end\"\n >\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={handleDeleteClose}\n disabled={isDeleting}\n >\n Cancel\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"error\"\n onClick={handleDelete}\n disabled={isDeleting}\n >\n {isDeleting ? \"Deleting...\" : \"Delete\"}\n </Button>\n </Stack>\n </Box>\n </Popover>\n </>\n )}\n </Stack>\n )}\n </Box>\n )}\n </Box>\n </Box>\n );\n}\n\nconst CommentEvent = memo(CommentEventComponent);\nCommentEvent.displayName = \"CommentEvent\";\n\n/**\n * TimelineEvent Component\n *\n * A pure presentation component for rendering timeline events.\n * Supports different event types including comments with edit/delete functionality.\n *\n * @example Basic usage with state change events\n * ```tsx\n * import { TimelineEvent } from '@datarecce/ui/primitives';\n *\n * function CheckTimeline({ events }) {\n * return (\n * <div>\n * {events.map(event => (\n * <TimelineEvent key={event.id} event={event} />\n * ))}\n * </div>\n * );\n * }\n * ```\n *\n * @example With comment editing\n * ```tsx\n * <TimelineEvent\n * event={commentEvent}\n * currentUserId={currentUser.id}\n * onEdit={async (eventId, content) => {\n * await updateComment(eventId, content);\n * }}\n * onDelete={async (eventId) => {\n * await deleteComment(eventId);\n * }}\n * />\n * ```\n *\n * @example With custom markdown renderer\n * ```tsx\n * import { MarkdownContent } from '@/components/ui/markdown/MarkdownContent';\n *\n * <TimelineEvent\n * event={event}\n * markdownRenderer={({ content }) => (\n * <MarkdownContent content={content} fontSize=\"sm\" />\n * )}\n * />\n * ```\n */\nfunction TimelineEventComponent({\n event,\n currentUserId,\n onEdit,\n onDelete,\n markdownRenderer,\n className,\n}: TimelineEventProps) {\n if (event.event_type === \"comment\") {\n return (\n <Box className={className}>\n <CommentEvent\n event={event}\n currentUserId={currentUserId}\n onEdit={onEdit}\n onDelete={onDelete}\n markdownRenderer={markdownRenderer}\n />\n </Box>\n );\n }\n\n return (\n <Box className={className}>\n <StateChangeEvent event={event} />\n </Box>\n );\n}\n\nexport const TimelineEvent = memo(TimelineEventComponent);\nTimelineEvent.displayName = \"TimelineEvent\";\n","/**\n * Utility functions for check components\n */\n\n/**\n * Build a title string for a check with optional approval indicator\n *\n * @example\n * ```ts\n * const title = buildCheckTitle({ name: \"Schema check\", isChecked: true });\n * // Returns: \"✅ Schema check\"\n * ```\n */\nexport function buildCheckTitle({\n name,\n isChecked,\n}: {\n name: string;\n isChecked?: boolean;\n}): string {\n return `${isChecked ? \"✅ \" : \"\"}${name}`;\n}\n\n/**\n * Build a description string with fallback for empty descriptions\n *\n * @example\n * ```ts\n * const desc = buildCheckDescription({ description: \"\" });\n * // Returns: \"_(no description)_\"\n * ```\n */\nexport function buildCheckDescription({\n description,\n fallback = \"_(no description)_\",\n}: {\n description?: string | null;\n fallback?: string;\n}): string {\n return (description ?? \"\") || fallback;\n}\n\n/**\n * Check if a run result is missing or has an error\n *\n * Certain check types (schema_diff, lineage_diff) don't require results\n * to enable approval functionality.\n *\n * @example\n * ```ts\n * const disabled = isDisabledByNoResult({\n * type: \"row_count_diff\",\n * hasResult: false,\n * hasError: false,\n * });\n * // Returns: true\n *\n * const enabled = isDisabledByNoResult({\n * type: \"schema_diff\",\n * hasResult: false,\n * hasError: false,\n * });\n * // Returns: false (schema_diff doesn't require results)\n * ```\n */\nexport function isDisabledByNoResult({\n type,\n hasResult,\n hasError,\n}: {\n type: string;\n hasResult: boolean;\n hasError: boolean;\n}): boolean {\n // These types don't require results to enable approval\n if (type === \"schema_diff\" || type === \"lineage_diff\") {\n return false;\n }\n return !hasResult || hasError;\n}\n\n/**\n * Format SQL as a markdown code block\n *\n * @example\n * ```ts\n * const markdown = formatSqlAsMarkdown({ sql: \"SELECT * FROM users\" });\n * // Returns:\n * // **SQL**\n * // ```sql\n * // SELECT * FROM users\n * // ```\n * ```\n */\nexport function formatSqlAsMarkdown({\n sql,\n label = \"SQL\",\n}: {\n sql: string;\n label?: string;\n}): string {\n return `**${label}**\n\\`\\`\\`sql\n${sql}\n\\`\\`\\``;\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo } from \"react\";\nimport type { Run } from \"../../api\";\n\n/**\n * Run status types\n */\nexport type RunStatus = \"Running\" | \"Finished\" | \"Failed\" | \"Cancelled\";\n\n/**\n * Infer run status from Run object\n *\n * When status is not explicitly set, infers from result/error:\n * - Has result -> \"Finished\"\n * - Has error -> \"Failed\"\n * - Otherwise -> \"Finished\" (default)\n *\n * @param run - The run object to infer status from\n * @returns The inferred run status\n *\n * @example\n * ```tsx\n * const status = inferRunStatus(run);\n * // Returns run.status if set, otherwise infers from result/error\n * ```\n */\nexport function inferRunStatus(run: Run): RunStatus {\n if (run.status) {\n return run.status as RunStatus;\n }\n\n // Infer from result/error when status is missing\n if (run.result) {\n return \"Finished\";\n }\n if (run.error) {\n return \"Failed\";\n }\n\n // Default to finished\n return \"Finished\";\n}\n\n/**\n * Props for the RunStatusBadge component\n */\nexport interface RunStatusBadgeProps {\n /** Run status */\n status: RunStatus;\n /** Whether to show the spinner for running state */\n showSpinner?: boolean;\n /** Text size variant */\n size?: \"small\" | \"medium\";\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Get status display properties\n */\nfunction getStatusDisplay(status: RunStatus): { color: string; label: string } {\n switch (status) {\n case \"Running\":\n return { color: \"blue\", label: \"Running\" };\n case \"Finished\":\n return { color: \"green\", label: \"Finished\" };\n case \"Failed\":\n return { color: \"red\", label: \"Failed\" };\n case \"Cancelled\":\n return { color: \"grey\", label: \"Cancelled\" };\n default:\n return { color: \"green\", label: \"Finished\" };\n }\n}\n\n/**\n * RunStatusBadge Component\n *\n * A pure presentation component for displaying run status with color coding.\n *\n * @example Basic usage\n * ```tsx\n * import { RunStatusBadge } from '@datarecce/ui/primitives';\n *\n * function RunItem({ run }) {\n * return (\n * <div>\n * <span>{run.name}</span>\n * <RunStatusBadge status={run.status} />\n * </div>\n * );\n * }\n * ```\n *\n * @example With spinner\n * ```tsx\n * <RunStatusBadge\n * status=\"Running\"\n * showSpinner\n * />\n * ```\n */\nfunction RunStatusBadgeComponent({\n status,\n showSpinner = true,\n size = \"small\",\n className,\n}: RunStatusBadgeProps) {\n const { color, label } = getStatusDisplay(status);\n const isRunning = status === \"Running\";\n const fontSize = size === \"small\" ? \"0.75rem\" : \"0.875rem\";\n const spinnerSize = size === \"small\" ? 12 : 16;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 0.5,\n }}\n >\n {isRunning && showSpinner && (\n <CircularProgress size={spinnerSize} color=\"primary\" />\n )}\n <Typography\n component=\"span\"\n sx={{\n fontWeight: 500,\n fontSize,\n color: `${color}.500`,\n }}\n >\n {label}\n </Typography>\n </Box>\n );\n}\n\nexport const RunStatusBadge = memo(RunStatusBadgeComponent);\nRunStatusBadge.displayName = \"RunStatusBadge\";\n\n/**\n * Format date relative to today\n */\nexport function formatRunDate(date: Date | null): string | null {\n if (date == null) return null;\n\n const today = new Date();\n const yesterday = new Date();\n yesterday.setDate(today.getDate() - 1);\n\n if (today.toDateString() === date.toDateString()) {\n return \"Today\";\n } else if (yesterday.toDateString() === date.toDateString()) {\n return \"Yesterday\";\n } else {\n return date.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" });\n }\n}\n\n/**\n * Format date and time relative to today\n */\nexport function formatRunDateTime(date: Date | null): string | null {\n if (date == null) return null;\n\n const today = new Date();\n const yesterday = new Date();\n yesterday.setDate(today.getDate() - 1);\n const time = date.toLocaleTimeString(\"en-US\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n\n if (today.toDateString() === date.toDateString()) {\n return `Today, ${time}`;\n } else if (yesterday.toDateString() === date.toDateString()) {\n return `Yesterday, ${time}`;\n } else {\n const dateStr = date.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n });\n return `${dateStr}, ${time}`;\n }\n}\n\n/**\n * Props for RunStatusWithDate component\n */\nexport interface RunStatusWithDateProps {\n /** Run status */\n status: RunStatus;\n /** Run timestamp */\n runAt?: string | Date;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunStatusWithDate Component\n *\n * Displays status badge with formatted date/time.\n *\n * @example\n * ```tsx\n * <RunStatusWithDate\n * status=\"Finished\"\n * runAt={run.run_at}\n * />\n * ```\n */\nfunction RunStatusWithDateComponent({\n status,\n runAt,\n className,\n}: RunStatusWithDateProps) {\n const dateTime = runAt\n ? formatRunDateTime(typeof runAt === \"string\" ? new Date(runAt) : runAt)\n : null;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 0.5,\n fontSize: \"0.75rem\",\n color: \"text.secondary\",\n }}\n >\n <RunStatusBadge status={status} size=\"small\" />\n {dateTime && (\n <>\n <Typography component=\"span\" sx={{ fontSize: \"inherit\" }}>\n •\n </Typography>\n <Typography\n component=\"span\"\n sx={{\n fontSize: \"inherit\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {dateTime}\n </Typography>\n </>\n )}\n </Box>\n );\n}\n\nexport const RunStatusWithDate = memo(RunStatusWithDateComponent);\nRunStatusWithDate.displayName = \"RunStatusWithDate\";\n\n/**\n * Props for RunStatusAndDate component\n */\nexport interface RunStatusAndDateProps {\n /** The run object to display status and date for */\n run: Run;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunStatusAndDate Component\n *\n * Displays run status badge with formatted date/time.\n * Accepts a Run object and infers status when not explicitly set.\n *\n * @example\n * ```tsx\n * import { RunStatusAndDate } from '@datarecce/ui/components/run';\n *\n * function RunItem({ run }) {\n * return (\n * <div>\n * <span>{run.name}</span>\n * <RunStatusAndDate run={run} />\n * </div>\n * );\n * }\n * ```\n */\nfunction RunStatusAndDateComponent({ run, className }: RunStatusAndDateProps) {\n const status = inferRunStatus(run);\n\n return (\n <RunStatusWithDate\n status={status}\n runAt={run.run_at}\n className={className}\n />\n );\n}\n\nexport const RunStatusAndDate = memo(RunStatusAndDateComponent);\nRunStatusAndDate.displayName = \"RunStatusAndDate\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Stack from \"@mui/material/Stack\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode } from \"react\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\nimport {\n formatRunDate,\n type RunStatus,\n RunStatusWithDate,\n} from \"./RunStatusBadge\";\n\n/**\n * Run data for list display\n */\nexport interface RunListItemData {\n /** Unique run ID */\n id: string;\n /** Run name */\n name?: string;\n /** Run type identifier */\n type: string;\n /** Run status */\n status: RunStatus;\n /** Run timestamp */\n runAt?: string;\n /** Associated check ID (if linked to a check) */\n checkId?: string;\n}\n\n/**\n * Props for a single run list item\n */\nexport interface RunListItemProps {\n /** Run data */\n run: RunListItemData;\n /** Whether this item is selected */\n isSelected?: boolean;\n /** Icon for the run type */\n icon?: ReactNode;\n /** Callback when item is clicked */\n onClick?: (runId: string) => void;\n /** Callback when \"add to checklist\" is clicked */\n onAddToChecklist?: (runId: string) => void;\n /** Callback when \"go to check\" is clicked */\n onGoToCheck?: (checkId: string) => void;\n /** Icon for \"add to checklist\" action */\n addToChecklistIcon?: ReactNode;\n /** Icon for \"go to check\" action */\n goToCheckIcon?: ReactNode;\n /** Hide add to checklist action */\n hideAddToChecklist?: boolean;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunListItem Component\n *\n * A single item in a run list with selection state and actions.\n *\n * @example\n * ```tsx\n * <RunListItem\n * run={run}\n * isSelected={selectedRunId === run.id}\n * icon={<QueryIcon />}\n * onClick={(id) => setSelectedRunId(id)}\n * onAddToChecklist={(id) => addRunToChecklist(id)}\n * />\n * ```\n */\nfunction RunListItemComponent({\n run,\n isSelected = false,\n icon,\n onClick,\n onAddToChecklist,\n onGoToCheck,\n addToChecklistIcon,\n goToCheckIcon,\n hideAddToChecklist = false,\n className,\n}: RunListItemProps) {\n const isDark = useIsDark();\n const hasCheckLink = run.checkId != null;\n const showAddToChecklist =\n !hideAddToChecklist && !hasCheckLink && onAddToChecklist;\n\n return (\n <Box\n className={className}\n onClick={() => onClick?.(run.id)}\n sx={(theme) => ({\n display: \"flex\",\n flexDirection: \"column\",\n width: \"100%\",\n p: \"8px 16px\",\n cursor: \"pointer\",\n borderBottom: 1,\n borderColor: \"divider\",\n borderLeft: \"4px solid\",\n borderLeftColor: isSelected ? \"warning.main\" : \"transparent\",\n bgcolor: isSelected\n ? isDark\n ? \"warning.dark\"\n : \"warning.light\"\n : \"transparent\",\n \"&:hover\": {\n bgcolor: isSelected\n ? isDark\n ? \"warning.dark\"\n : \"warning.light\"\n : theme.palette.action.hover,\n },\n })}\n >\n {/* Name and actions row */}\n <Box sx={{ display: \"flex\", alignItems: \"center\", gap: 1.5 }}>\n {icon && (\n <Box sx={{ fontSize: 16, color: \"text.secondary\", flexShrink: 0 }}>\n {icon}\n </Box>\n )}\n <Typography\n sx={{\n flex: 1,\n fontSize: \"0.875rem\",\n fontWeight: 500,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n color: run.name ? \"text.primary\" : \"text.secondary\",\n }}\n >\n {run.name?.trim() || \"<no name>\"}\n </Typography>\n\n {/* Check link or add to checklist */}\n {hasCheckLink && onGoToCheck && (\n <Tooltip title=\"Go to Check\">\n <IconButton\n size=\"small\"\n onClick={(e) => {\n e.stopPropagation();\n if (run.checkId) onGoToCheck(run.checkId);\n }}\n sx={{ p: 0.5 }}\n >\n {goToCheckIcon || (\n <Box\n component=\"span\"\n sx={{ fontSize: 14, color: \"success.main\" }}\n >\n ✓\n </Box>\n )}\n </IconButton>\n </Tooltip>\n )}\n {showAddToChecklist && (\n <Tooltip title=\"Add to Checklist\">\n <IconButton\n size=\"small\"\n onClick={(e) => {\n e.stopPropagation();\n onAddToChecklist(run.id);\n }}\n sx={{ p: 0.5 }}\n >\n {addToChecklistIcon || (\n <Box component=\"span\" sx={{ fontSize: 14 }}>\n ○\n </Box>\n )}\n </IconButton>\n </Tooltip>\n )}\n </Box>\n\n {/* Status and date row */}\n <RunStatusWithDate status={run.status} runAt={run.runAt} />\n </Box>\n );\n}\n\nexport const RunListItem = memo(RunListItemComponent);\nRunListItem.displayName = \"RunListItem\";\n\n/**\n * Props for RunList component\n */\nexport interface RunListProps {\n /** List of runs to display */\n runs: RunListItemData[];\n /** Currently selected run ID */\n selectedId?: string | null;\n /** Whether the list is loading */\n isLoading?: boolean;\n /** Callback when a run is selected */\n onRunSelect?: (runId: string) => void;\n /** Callback when \"add to checklist\" is clicked */\n onAddToChecklist?: (runId: string) => void;\n /** Callback when \"go to check\" is clicked */\n onGoToCheck?: (checkId: string) => void;\n /** Function to get icon for run type */\n getRunIcon?: (runType: string) => ReactNode;\n /** Hide add to checklist action */\n hideAddToChecklist?: boolean;\n /** List title */\n title?: string;\n /** Header action buttons */\n headerActions?: ReactNode;\n /** Empty state message */\n emptyMessage?: string;\n /** Loading state message */\n loadingMessage?: string;\n /** Group runs by date */\n groupByDate?: boolean;\n /** Icon for \"add to checklist\" action - passed to all list items */\n addToChecklistIcon?: ReactNode;\n /** Icon for \"go to check\" action - passed to all list items */\n goToCheckIcon?: ReactNode;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Date segment divider\n */\ninterface DateSegmentProps {\n date: string;\n}\n\nconst DateSegment = memo(function DateSegment({ date }: DateSegmentProps) {\n return (\n <Box\n sx={{\n width: \"100%\",\n p: \"8px 16px\",\n borderBottom: 1,\n borderColor: \"divider\",\n color: \"text.secondary\",\n fontSize: \"0.75rem\",\n fontWeight: 500,\n bgcolor: \"action.hover\",\n }}\n >\n {date}\n </Box>\n );\n});\n\n/**\n * RunList Component\n *\n * A pure presentation component for displaying a list of runs with\n * selection, actions, and optional date grouping.\n *\n * @example Basic usage\n * ```tsx\n * import { RunList } from '@datarecce/ui/primitives';\n *\n * function HistoryPanel({ runs }) {\n * const [selectedId, setSelectedId] = useState(null);\n *\n * return (\n * <RunList\n * runs={runs}\n * selectedId={selectedId}\n * onRunSelect={setSelectedId}\n * title=\"History\"\n * groupByDate\n * />\n * );\n * }\n * ```\n *\n * @example With custom icons\n * ```tsx\n * <RunList\n * runs={runs}\n * selectedId={selectedId}\n * onRunSelect={setSelectedId}\n * getRunIcon={(type) => {\n * switch (type) {\n * case 'query': return <SqlIcon />;\n * case 'profile': return <ProfileIcon />;\n * default: return <DefaultIcon />;\n * }\n * }}\n * />\n * ```\n */\nfunction RunListComponent({\n runs,\n selectedId,\n isLoading = false,\n onRunSelect,\n onAddToChecklist,\n onGoToCheck,\n getRunIcon,\n hideAddToChecklist = false,\n title = \"Runs\",\n headerActions,\n emptyMessage = \"No runs\",\n loadingMessage = \"Loading...\",\n groupByDate = false,\n addToChecklistIcon,\n goToCheckIcon,\n className,\n}: RunListProps) {\n // Group runs by date if needed\n const renderRuns = () => {\n if (runs.length === 0) {\n return (\n <Box\n sx={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n height: \"100%\",\n color: \"text.secondary\",\n p: 4,\n }}\n >\n {emptyMessage}\n </Box>\n );\n }\n\n let previousDate: string | null = null;\n return runs.map((run) => {\n const currentDate = run.runAt ? formatRunDate(new Date(run.runAt)) : null;\n const showDateSegment =\n groupByDate && currentDate && previousDate !== currentDate;\n previousDate = currentDate;\n\n return (\n <Box key={run.id}>\n {showDateSegment && <DateSegment date={currentDate} />}\n <RunListItem\n run={run}\n isSelected={run.id === selectedId}\n icon={getRunIcon?.(run.type)}\n onClick={onRunSelect}\n onAddToChecklist={onAddToChecklist}\n onGoToCheck={onGoToCheck}\n hideAddToChecklist={hideAddToChecklist}\n addToChecklistIcon={addToChecklistIcon}\n goToCheckIcon={goToCheckIcon}\n />\n </Box>\n );\n });\n };\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n height: \"100%\",\n width: \"100%\",\n }}\n >\n {/* Header */}\n <Stack\n direction=\"row\"\n alignItems=\"center\"\n sx={{\n flex: \"0 0 auto\",\n px: 2,\n py: 1.5,\n borderBottom: 1,\n borderColor: \"divider\",\n }}\n >\n <Typography variant=\"h6\">{title}</Typography>\n <Box sx={{ flex: 1 }} />\n {headerActions}\n </Stack>\n\n {/* List content */}\n <Box\n sx={{\n flex: 1,\n overflow: \"auto\",\n }}\n >\n {isLoading ? (\n <Box\n sx={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n height: \"100%\",\n color: \"text.secondary\",\n }}\n >\n {loadingMessage}\n </Box>\n ) : (\n renderRuns()\n )}\n </Box>\n </Box>\n );\n}\n\nexport const RunList = memo(RunListComponent);\nRunList.displayName = \"RunList\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport LinearProgress from \"@mui/material/LinearProgress\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode } from \"react\";\nimport { type RunStatus, RunStatusBadge } from \"./RunStatusBadge\";\n\n/**\n * Progress variant types\n */\nexport type RunProgressVariant = \"spinner\" | \"linear\" | \"circular\";\n\n/**\n * Props for the RunProgress component\n */\nexport interface RunProgressProps {\n /** Run status */\n status: RunStatus;\n /** Progress percentage (0-100) for determinate progress */\n progress?: number;\n /** Progress message (e.g., \"Querying base...\") */\n message?: string;\n /** Error message when status is 'failed' */\n errorMessage?: string;\n /** Progress display variant */\n variant?: RunProgressVariant;\n /** Show status badge */\n showStatus?: boolean;\n /** Optional icon to display */\n icon?: ReactNode;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunProgress Component\n *\n * A pure presentation component for displaying run progress with\n * optional progress bar, message, and status.\n *\n * @example Basic spinner\n * ```tsx\n * import { RunProgress } from '@datarecce/ui/primitives';\n *\n * function RunningIndicator({ run }) {\n * return (\n * <RunProgress\n * status={run.status}\n * message=\"Executing query...\"\n * />\n * );\n * }\n * ```\n *\n * @example With progress bar\n * ```tsx\n * <RunProgress\n * status=\"Running\"\n * variant=\"linear\"\n * progress={65}\n * message=\"Processing records: 65,000 / 100,000\"\n * />\n * ```\n *\n * @example Error state\n * ```tsx\n * <RunProgress\n * status=\"Failed\"\n * errorMessage=\"Connection timeout after 30 seconds\"\n * />\n * ```\n */\nfunction RunProgressComponent({\n status,\n progress,\n message,\n errorMessage,\n variant = \"spinner\",\n showStatus = true,\n icon,\n className,\n}: RunProgressProps) {\n const isRunning = status === \"Running\";\n const isFailed = status === \"Failed\";\n const hasProgress = progress !== undefined && progress >= 0;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 2,\n p: 3,\n textAlign: \"center\",\n }}\n >\n {/* Icon or Progress indicator */}\n {icon ? (\n <Box sx={{ fontSize: 40, color: \"text.secondary\" }}>{icon}</Box>\n ) : (\n isRunning && (\n <>\n {variant === \"spinner\" && (\n <CircularProgress size={40} color=\"primary\" />\n )}\n {variant === \"circular\" && (\n <CircularProgress\n size={60}\n variant={hasProgress ? \"determinate\" : \"indeterminate\"}\n value={progress}\n color=\"primary\"\n />\n )}\n </>\n )\n )}\n\n {/* Linear progress bar */}\n {isRunning && variant === \"linear\" && (\n <Box sx={{ width: \"100%\", maxWidth: 300 }}>\n <LinearProgress\n variant={hasProgress ? \"determinate\" : \"indeterminate\"}\n value={progress}\n sx={{ height: 8, borderRadius: 4 }}\n />\n {hasProgress && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ mt: 0.5, display: \"block\" }}\n >\n {Math.round(progress)}%\n </Typography>\n )}\n </Box>\n )}\n\n {/* Status badge */}\n {showStatus && <RunStatusBadge status={status} size=\"medium\" />}\n\n {/* Message */}\n {message && !isFailed && (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {message}\n </Typography>\n )}\n\n {/* Error message */}\n {isFailed && errorMessage && (\n <Box\n sx={{\n p: 2,\n bgcolor: \"error.light\",\n borderRadius: 1,\n maxWidth: 400,\n }}\n >\n <Typography variant=\"body2\" color=\"error.contrastText\">\n {errorMessage}\n </Typography>\n </Box>\n )}\n </Box>\n );\n}\n\nexport const RunProgress = memo(RunProgressComponent);\nRunProgress.displayName = \"RunProgress\";\n\n/**\n * Props for RunProgressOverlay component\n */\nexport interface RunProgressOverlayProps extends RunProgressProps {\n /** Whether the overlay is visible */\n visible?: boolean;\n /** Background opacity (0-1) */\n opacity?: number;\n}\n\n/**\n * RunProgressOverlay Component\n *\n * A full-container overlay version of RunProgress.\n *\n * @example\n * ```tsx\n * <div style={{ position: 'relative', height: 400 }}>\n * <YourContent />\n * <RunProgressOverlay\n * visible={isLoading}\n * status=\"Running\"\n * message=\"Loading data...\"\n * />\n * </div>\n * ```\n */\nfunction RunProgressOverlayComponent({\n visible = true,\n opacity = 0.8,\n ...progressProps\n}: RunProgressOverlayProps) {\n if (!visible) return null;\n\n return (\n <Box\n sx={{\n position: \"absolute\",\n inset: 0,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: `rgba(255, 255, 255, ${opacity})`,\n zIndex: 10,\n \".dark &\": {\n bgcolor: `rgba(0, 0, 0, ${opacity})`,\n },\n }}\n >\n <RunProgress {...progressProps} />\n </Box>\n );\n}\n\nexport const RunProgressOverlay = memo(RunProgressOverlayComponent);\nRunProgressOverlay.displayName = \"RunProgressOverlay\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport { memo, type ReactNode } from \"react\";\nimport { PiWarning } from \"react-icons/pi\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\nimport { colors } from \"../../theme/colors\";\n\n/**\n * Common view options for diff-based result views.\n * Used to control what data is shown in diff results.\n */\nexport interface DiffViewOptions {\n /** When true, only show rows/columns that have changed */\n changed_only?: boolean;\n}\n\n/**\n * Props for the RunToolbar component\n */\nexport interface RunToolbarProps {\n /** Array of warning messages to display */\n warnings?: string[];\n /** Toolbar actions or other content */\n children?: ReactNode;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * RunToolbar Component\n *\n * A pure presentation component for displaying run toolbar with warnings.\n * Warnings are displayed with amber background when present.\n *\n * @example Basic usage with warnings\n * ```tsx\n * import { RunToolbar } from '@datarecce/ui/primitives';\n *\n * function RunPane({ run }) {\n * return (\n * <RunToolbar warnings={run.warnings}>\n * <Button onClick={handleExport}>Export</Button>\n * </RunToolbar>\n * );\n * }\n * ```\n *\n * @example Without warnings\n * ```tsx\n * <RunToolbar>\n * <Switch label=\"Changed only\" />\n * <Button onClick={handleCopy}>Copy</Button>\n * </RunToolbar>\n * ```\n */\nfunction RunToolbarComponent({\n warnings,\n children,\n className,\n}: RunToolbarProps) {\n const isDark = useIsDark();\n const hasWarnings = warnings && warnings.length > 0;\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n borderBottom: \"1px solid\",\n borderColor: \"divider\",\n justifyContent: \"flex-end\",\n gap: \"5px\",\n alignItems: \"center\",\n px: \"10px\",\n bgcolor: hasWarnings\n ? isDark\n ? colors.amber[900]\n : colors.amber[100]\n : \"inherit\",\n color: hasWarnings\n ? isDark\n ? colors.amber[200]\n : colors.amber[800]\n : \"inherit\",\n }}\n >\n <Box\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"flex-start\",\n gap: 0,\n }}\n >\n {warnings?.map((warning, index) => (\n <Box key={warning}>\n <PiWarning\n color={isDark ? colors.amber[400] : colors.amber[600]}\n style={{ verticalAlign: \"middle\", marginRight: 4 }}\n />\n {warning}\n </Box>\n ))}\n </Box>\n <Box sx={{ flex: 1, minHeight: \"32px\" }} />\n {children}\n </Box>\n );\n}\n\nexport const RunToolbar = memo(RunToolbarComponent);\nRunToolbar.displayName = \"RunToolbar\";\n","\"use client\";\n\nimport {\n BarElement,\n CategoryScale,\n type ChartData,\n Chart as ChartJS,\n type ChartOptions,\n Legend,\n LinearScale,\n TimeSeriesScale,\n Title,\n Tooltip,\n} from \"chart.js\";\nimport { memo, useMemo } from \"react\";\nimport { Chart } from \"react-chartjs-2\";\n\n// Register Chart.js modules once\nChartJS.register(\n BarElement,\n TimeSeriesScale,\n LinearScale,\n CategoryScale,\n Title,\n Legend,\n Tooltip,\n);\n\n/**\n * Theme-aware colors for charts\n */\nexport interface ChartThemeColors {\n gridColor: string;\n textColor: string;\n borderColor: string;\n tooltipBackgroundColor: string;\n tooltipTextColor: string;\n /** Text color for labels drawn inside bars (must contrast with pastel bar fills) */\n barLabelColor: string;\n}\n\n/**\n * Bar colors for base/current comparison\n */\nexport interface ChartBarColors {\n current: string;\n base: string;\n currentWithAlpha: string;\n baseWithAlpha: string;\n}\n\n// Light mode colors\nconst CURRENT_BAR_COLOR = \"#63B3ED\";\nconst BASE_BAR_COLOR = \"#F6AD55\";\nconst CURRENT_BAR_COLOR_WITH_ALPHA = `${CURRENT_BAR_COLOR}A5`;\nconst BASE_BAR_COLOR_WITH_ALPHA = `${BASE_BAR_COLOR}A5`;\n\n// Dark mode colors\nconst CURRENT_BAR_COLOR_DARK = \"#90CDF4\";\nconst BASE_BAR_COLOR_DARK = \"#FBD38D\";\nconst CURRENT_BAR_COLOR_DARK_WITH_ALPHA = `${CURRENT_BAR_COLOR_DARK}A5`;\nconst BASE_BAR_COLOR_DARK_WITH_ALPHA = `${BASE_BAR_COLOR_DARK}A5`;\n\n/**\n * Get theme-aware colors for charts\n */\nexport function getChartThemeColors(isDark: boolean): ChartThemeColors {\n return {\n gridColor: isDark ? \"#4b5563\" : \"#d1d5db\",\n textColor: isDark ? \"#e5e7eb\" : \"#374151\",\n borderColor: isDark ? \"#6b7280\" : \"#9ca3af\",\n tooltipBackgroundColor: isDark ? \"#1f2937\" : \"#ffffff\",\n tooltipTextColor: isDark ? \"#e5e7eb\" : \"#111827\",\n barLabelColor: \"#1f2937\",\n };\n}\n\n/**\n * Get theme-aware bar colors\n */\nexport function getChartBarColors(isDark: boolean): ChartBarColors {\n return {\n current: isDark ? CURRENT_BAR_COLOR_DARK : CURRENT_BAR_COLOR,\n base: isDark ? BASE_BAR_COLOR_DARK : BASE_BAR_COLOR,\n currentWithAlpha: isDark\n ? CURRENT_BAR_COLOR_DARK_WITH_ALPHA\n : CURRENT_BAR_COLOR_WITH_ALPHA,\n baseWithAlpha: isDark\n ? BASE_BAR_COLOR_DARK_WITH_ALPHA\n : BASE_BAR_COLOR_WITH_ALPHA,\n };\n}\n\n/**\n * Histogram dataset for a single environment\n */\nexport interface HistogramDataset {\n /** Count values per bin */\n counts: number[];\n /** Optional dataset label */\n label?: string;\n}\n\n/**\n * Histogram data type\n */\nexport type HistogramDataType = \"numeric\" | \"datetime\" | \"string\";\n\n/**\n * Props for the HistogramChart component\n */\nexport interface HistogramChartProps {\n /** Chart title */\n title: string;\n /** Data type (numeric, datetime, or string) */\n dataType?: HistogramDataType;\n /** Total sample count */\n samples?: number;\n /** Minimum value (for datetime scale) */\n min?: string | number;\n /** Maximum value (for datetime scale) */\n max?: string | number;\n /** Bin edge values */\n binEdges: number[];\n /** Base environment dataset */\n baseData: HistogramDataset;\n /** Current environment dataset */\n currentData: HistogramDataset;\n /** Enable animation */\n animate?: boolean;\n /** Hide axis labels and ticks */\n hideAxis?: boolean;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Chart height in pixels */\n height?: number;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Format number as abbreviated (K, M, B, T)\n */\nfunction formatAbbreviatedNumber(input: number | string): string {\n if (typeof input !== \"number\") return String(input);\n\n const absValue = Math.abs(input);\n const trillion = 1e12;\n const billion = 1e9;\n const million = 1e6;\n const thousand = 1e3;\n\n if (absValue >= trillion) {\n return `${(input / trillion).toFixed(1)}T`;\n }\n if (absValue >= billion) {\n return `${(input / billion).toFixed(1)}B`;\n }\n if (absValue >= million) {\n return `${(input / million).toFixed(1)}M`;\n }\n if (absValue >= thousand) {\n return `${(input / thousand).toFixed(1)}K`;\n }\n if (absValue >= 1) {\n return input.toFixed(2);\n }\n if (absValue >= 0.01) {\n return input.toFixed(3);\n }\n return input.toExponential(2);\n}\n\n/**\n * Format bin range display\n */\nfunction formatBinRange(binEdges: number[], index: number): string {\n const start = binEdges[index];\n const end = binEdges[index + 1];\n return `${formatAbbreviatedNumber(start)} - ${formatAbbreviatedNumber(end)}`;\n}\n\n/**\n * Format percentage display\n */\nfunction formatPercentage(value: number): string {\n if (value > 0 && value <= 0.001) return \"<0.1%\";\n if (value < 1 && value >= 0.999) return \">99.9%\";\n return `${(value * 100).toFixed(1)}%`;\n}\n\n/**\n * HistogramChart Component\n *\n * A pure presentation component for displaying histogram charts comparing\n * base and current data distributions using Chart.js.\n *\n * @example Basic usage\n * ```tsx\n * import { HistogramChart } from '@datarecce/ui/primitives';\n *\n * function ProfilePanel({ histogramData }) {\n * return (\n * <HistogramChart\n * title=\"Age Distribution\"\n * dataType=\"numeric\"\n * binEdges={histogramData.binEdges}\n * baseData={{ counts: histogramData.baseCounts }}\n * currentData={{ counts: histogramData.currentCounts }}\n * samples={1000}\n * />\n * );\n * }\n * ```\n *\n * @example With datetime scale\n * ```tsx\n * <HistogramChart\n * title=\"Events Over Time\"\n * dataType=\"datetime\"\n * binEdges={timestamps}\n * baseData={{ counts: baseCounts }}\n * currentData={{ counts: currentCounts }}\n * min={startDate}\n * max={endDate}\n * />\n * ```\n */\nfunction HistogramChartComponent({\n title,\n dataType = \"numeric\",\n samples = 0,\n min = 0,\n max = 0,\n binEdges,\n baseData,\n currentData,\n animate = false,\n hideAxis = false,\n theme = \"light\",\n height = 300,\n className,\n}: HistogramChartProps) {\n const isDark = theme === \"dark\";\n const themeColors = getChartThemeColors(isDark);\n const barColors = getChartBarColors(isDark);\n const isDatetime = dataType === \"datetime\";\n\n // Build chart data\n const chartData = useMemo<ChartData<\"bar\">>(() => {\n const labels = binEdges\n .slice(0, -1)\n .map((_, i) => formatBinRange(binEdges, i));\n\n const buildDataset = (\n data: HistogramDataset,\n label: string,\n color: string,\n ) => {\n const counts = data.counts ?? [];\n const chartValues = isDatetime\n ? counts.map((v, i) => [binEdges[i], v] as [number, number])\n : counts;\n\n return {\n label,\n data: chartValues as number[],\n backgroundColor: color,\n borderColor: color,\n hoverBackgroundColor: color,\n borderWidth: 0,\n categoryPercentage: 1,\n barPercentage: 1,\n xAxisID: \"x\",\n };\n };\n\n return {\n labels,\n datasets: [\n buildDataset(\n currentData,\n currentData.label ?? \"Current\",\n barColors.currentWithAlpha,\n ),\n buildDataset(\n baseData,\n baseData.label ?? \"Base\",\n barColors.baseWithAlpha,\n ),\n ],\n };\n }, [binEdges, baseData, currentData, barColors, isDatetime]);\n\n // Build chart options\n const chartOptions = useMemo<ChartOptions<\"bar\">>(() => {\n const maxCount = Math.max(...currentData.counts, ...baseData.counts);\n\n const labels = binEdges\n .slice(0, -1)\n .map((_, i) => formatBinRange(binEdges, i));\n const dataTypeLabel = isDatetime\n ? \"Date Range\"\n : dataType === \"string\"\n ? \"Text Length\"\n : \"Value Range\";\n\n return {\n responsive: true,\n maintainAspectRatio: false,\n animation: animate ? undefined : false,\n plugins: {\n legend: {\n reverse: true,\n labels: {\n color: themeColors.textColor,\n },\n },\n title: {\n display: true,\n text: title,\n font: { size: 20 },\n color: themeColors.textColor,\n },\n tooltip: {\n mode: \"index\",\n intersect: false,\n backgroundColor: themeColors.tooltipBackgroundColor,\n titleColor: themeColors.tooltipTextColor,\n bodyColor: themeColors.tooltipTextColor,\n borderColor: themeColors.borderColor,\n borderWidth: 1,\n callbacks: {\n title([{ dataIndex }]) {\n const range = formatBinRange(binEdges, dataIndex);\n return `${dataTypeLabel}\\n${range}`;\n },\n label({ datasetIndex, dataIndex, dataset }) {\n const counts =\n datasetIndex === 0 ? currentData.counts : baseData.counts;\n const count = counts[dataIndex];\n const percent =\n samples > 0 ? formatPercentage(count / samples) : \"\";\n return `${dataset.label}: ${count}${percent ? ` (${percent})` : \"\"}`;\n },\n },\n },\n },\n scales: {\n x: isDatetime\n ? {\n display: !hideAxis,\n type: \"timeseries\",\n min,\n max,\n adapters: { date: {} },\n time: { minUnit: \"day\" },\n grid: { display: false },\n ticks: {\n minRotation: 30,\n maxRotation: 30,\n maxTicksLimit: 8,\n color: themeColors.textColor,\n },\n }\n : {\n display: !hideAxis,\n type: \"category\",\n grid: { display: false },\n ticks: {\n callback(_val, index) {\n return labels[index];\n },\n color: themeColors.textColor,\n },\n stacked: true,\n },\n y: {\n display: !hideAxis,\n type: \"linear\",\n max: maxCount,\n border: { dash: [2, 2], color: themeColors.borderColor },\n grid: { color: themeColors.gridColor },\n ticks: {\n maxTicksLimit: 8,\n color: themeColors.textColor,\n callback(val) {\n return formatAbbreviatedNumber(val as number);\n },\n },\n beginAtZero: true,\n },\n },\n };\n }, [\n title,\n dataType,\n isDatetime,\n samples,\n min,\n max,\n binEdges,\n baseData,\n currentData,\n hideAxis,\n animate,\n themeColors,\n ]);\n\n return (\n <div className={className} style={{ height }}>\n <Chart type=\"bar\" options={chartOptions} data={chartData} />\n </div>\n );\n}\n\nexport const HistogramChart = memo(HistogramChartComponent);\nHistogramChart.displayName = \"HistogramChart\";\n","\"use client\";\n\n/**\n * @file ScreenshotDataGrid.tsx\n * @description AG Grid wrapper component with screenshot support\n *\n * This component wraps AG Grid and provides:\n * - Theme switching (light/dark)\n * - Default grid configurations\n * - Screenshot capture support via ref\n * - Backward compatibility with react-data-grid API\n */\n\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport type {\n ColDef,\n ColGroupDef,\n GetRowIdParams,\n GridReadyEvent,\n} from \"ag-grid-community\";\nimport { AllCommunityModule, ModuleRegistry } from \"ag-grid-community\";\nimport { AgGridReact, type AgGridReactProps } from \"ag-grid-react\";\nimport React, {\n type CSSProperties,\n forwardRef,\n type Ref,\n useImperativeHandle,\n useMemo,\n useRef,\n} from \"react\";\n\nimport { useIsDark } from \"../../hooks\";\nimport \"./agGridStyles.css\";\nimport { dataGridThemeDark, dataGridThemeLight } from \"./agGridTheme\";\n\n// Register AG Grid modules once\nModuleRegistry.registerModules([AllCommunityModule]);\n\n/**\n * Handle type for accessing AG Grid API and DOM element (for screenshots)\n */\nexport interface DataGridHandle {\n api: GridReadyEvent[\"api\"] | null;\n /** DOM element for screenshot functionality */\n element: HTMLElement | null;\n}\n\n/**\n * Generic row type for data grids\n */\nexport interface DataGridRow {\n __rowKey?: string | number;\n [key: string]: unknown;\n}\n\n/**\n * Props for ScreenshotDataGrid component\n *\n * Supports both AG Grid style props (columnDefs/rowData) and\n * legacy react-data-grid style props (columns/rows) for backward compatibility\n */\nexport interface ScreenshotDataGridProps<TData = DataGridRow>\n extends Omit<AgGridReactProps<TData>, \"theme\" | \"rowClass\"> {\n /** Container style */\n style?: CSSProperties;\n /** Additional CSS class for container */\n className?: string;\n /** Empty state renderer (legacy) */\n renderers?: {\n noRowsFallback?: React.ReactNode;\n };\n /** Legacy: Column definitions (maps to columnDefs) */\n columns?: (ColDef<TData> | ColGroupDef<TData>)[];\n /** Legacy: Row data (maps to rowData) */\n rows?: TData[];\n /** Legacy: Default column options (maps to defaultColDef) */\n defaultColumnOptions?: ColDef<TData>;\n /** Optional CSS class to apply to rows (e.g., for PII tracking) */\n rowClassName?: string;\n /** Optional CSS class to apply to container (e.g., for PII tracking) */\n containerClassName?: string;\n}\n\n/**\n * Empty rows renderer component\n */\nexport interface EmptyRowsRendererProps {\n emptyMessage?: string;\n}\n\nexport function EmptyRowsRenderer({ emptyMessage }: EmptyRowsRendererProps) {\n return (\n <Box\n sx={{\n display: \"flex\",\n height: \"35px\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: \"grey.100\",\n textAlign: \"center\",\n gridColumn: \"1/-1\",\n }}\n >\n <Typography sx={{ fontWeight: 600 }}>\n {emptyMessage ?? \"No rows\"}\n </Typography>\n </Box>\n );\n}\n\n/**\n * AG Grid wrapper component with screenshot support\n *\n * @description Provides a themed AG Grid with default configurations:\n * - Automatic light/dark theme switching\n * - Row hover highlighting disabled by default\n * - Cell focus suppressed for cleaner UX\n *\n * Backward compatible with react-data-grid API:\n * - `columns` prop maps to `columnDefs`\n * - `rows` prop maps to `rowData`\n * - `defaultColumnOptions` maps to `defaultColDef`\n * - `renderers.noRowsFallback` maps to `noRowsOverlayComponent`\n *\n * @example\n * ```tsx\n * // AG Grid style\n * <ScreenshotDataGrid\n * columnDefs={columns}\n * rowData={rows}\n * style={{ height: '400px' }}\n * />\n *\n * // Legacy react-data-grid style\n * <ScreenshotDataGrid\n * columns={columns}\n * rows={rows}\n * renderers={{ noRowsFallback: <EmptyRowsRenderer /> }}\n * />\n * ```\n */\nfunction _ScreenshotDataGrid<TData = DataGridRow>(\n {\n style,\n className,\n columnDefs,\n rowData,\n columns,\n rows,\n getRowId,\n rowHeight = 32,\n headerHeight = 36,\n defaultColDef,\n defaultColumnOptions,\n renderers,\n rowClassName,\n containerClassName,\n ...props\n }: ScreenshotDataGridProps<TData>,\n ref: Ref<DataGridHandle>,\n) {\n // Container ref for screenshot functionality\n const containerRef = useRef<HTMLDivElement>(null);\n // AG Grid API ref\n const gridApiRef = useRef<GridReadyEvent[\"api\"] | null>(null);\n\n // Expose both API and DOM element through ref\n useImperativeHandle(\n ref,\n () => ({\n api: gridApiRef.current,\n element: containerRef.current,\n }),\n [],\n );\n\n // Use useIsDark for reliable dark mode detection with CSS Variables\n const isDark = useIsDark();\n\n // Select AG Grid theme based on dark mode\n const gridTheme = useMemo(\n () => (isDark ? dataGridThemeDark : dataGridThemeLight),\n [isDark],\n );\n\n // Support both new and legacy props\n const resolvedColumnDefs = columnDefs ?? columns;\n const resolvedRowData = rowData ?? rows;\n const resolvedDefaultColDef = defaultColDef ?? defaultColumnOptions;\n\n // Merge default column options\n const mergedDefaultColDef = useMemo<ColDef<TData>>(\n () => ({\n resizable: true,\n suppressMovable: true,\n ...resolvedDefaultColDef,\n }),\n [resolvedDefaultColDef],\n );\n\n // Custom overlay component when no rows\n const noRowsOverlayComponent = useMemo(() => {\n if (!renderers?.noRowsFallback) return undefined;\n return () => renderers.noRowsFallback;\n }, [renderers?.noRowsFallback]);\n\n // Generate row ID from __rowKey if available\n const resolvedGetRowId = useMemo(() => {\n if (getRowId) return getRowId;\n return (params: GetRowIdParams<TData>) => {\n const data = params.data as DataGridRow;\n if (data?.__rowKey !== undefined) {\n return String(data.__rowKey);\n }\n // Use rowIndex from the data or generate a random ID\n const index = (params.data as unknown as { rowIndex?: number })?.rowIndex;\n return String(index ?? Math.random());\n };\n }, [getRowId]);\n\n // Generate a key based on pinned columns to force AG Grid to remount\n const gridKey = useMemo(() => {\n if (!resolvedColumnDefs) return \"grid\";\n const pinnedFields = resolvedColumnDefs\n .filter(\n (col): col is ColDef<TData> => \"field\" in col && col.pinned === \"left\",\n )\n .map((col) => col.field)\n .sort()\n .join(\",\");\n return `grid-${pinnedFields}`;\n }, [resolvedColumnDefs]);\n\n // Combine class names\n const combinedClassName = [className, containerClassName]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <Box\n ref={containerRef}\n className={combinedClassName || undefined}\n sx={{\n // Use flex: 1 and minHeight: 0 for proper sizing in flex containers\n flex: 1,\n minHeight: 0,\n width: \"100%\",\n overflow: \"hidden\",\n \"& .ag-root-wrapper\": {\n border: \"none\",\n height: \"100%\",\n },\n \"& .ag-header\": {\n borderBottom: \"1px solid var(--ag-border-color)\",\n },\n \"& .ag-row\": {\n borderBottom: \"1px solid var(--ag-border-color)\",\n },\n \"& .ag-cell\": {\n borderRight: \"1px solid var(--ag-border-color)\",\n },\n \"& .ag-header-cell\": {\n borderRight: \"1px solid var(--ag-border-color)\",\n },\n // Diff cell styling - theme-aware colors\n \"& .diff-cell-added\": {\n backgroundColor: isDark ? \"#1a4d1a !important\" : \"#cefece !important\",\n color: \"var(--mui-palette-text-primary)\",\n },\n \"& .diff-cell-removed\": {\n backgroundColor: isDark ? \"#5c1f1f !important\" : \"#ffc5c5 !important\",\n color: \"var(--mui-palette-text-primary)\",\n },\n \"& .diff-cell-modified\": {\n backgroundColor: isDark ? \"#713F12 !important\" : \"#FEF3C7 !important\",\n color: \"var(--mui-palette-text-primary)\",\n },\n // Diff header styling\n \"& .diff-header-added\": {\n backgroundColor: \"#15803d !important\",\n color: \"white\",\n },\n \"& .diff-header-removed\": {\n backgroundColor: \"#f43f5e !important\",\n color: \"white\",\n },\n \"& .diff-header-modified\": {\n backgroundColor: \"#f59e0b !important\",\n color: \"white\",\n },\n // Index column styling\n \"& .index-column\": {\n color: \"var(--mui-palette-text-secondary)\",\n textAlign: \"right\",\n },\n // Frozen/pinned column styling\n \"& .ag-pinned-left-cols-container .ag-cell\": {\n backgroundColor: isDark ? \"#2d2d2d\" : \"#f5f5f5\",\n },\n }}\n >\n <AgGridReact<TData>\n key={gridKey}\n theme={gridTheme}\n columnDefs={resolvedColumnDefs}\n rowData={resolvedRowData}\n getRowId={resolvedGetRowId}\n rowHeight={rowHeight}\n headerHeight={headerHeight}\n defaultColDef={mergedDefaultColDef}\n suppressCellFocus={true}\n suppressRowHoverHighlight={false}\n animateRows={false}\n rowClass={rowClassName}\n noRowsOverlayComponent={noRowsOverlayComponent}\n onGridReady={(event) => {\n gridApiRef.current = event.api;\n }}\n {...props}\n />\n </Box>\n );\n}\n\nexport const ScreenshotDataGrid = forwardRef(_ScreenshotDataGrid) as <\n TData = DataGridRow,\n>(\n props: ScreenshotDataGridProps<TData> & { ref?: Ref<DataGridHandle> },\n) => React.ReactNode;\n\n// Re-export AG Grid types for convenience\nexport type { ColDef, ColGroupDef, GetRowIdParams, GridReadyEvent };\n\n// For backward compatibility\nexport type { DataGridHandle as RecceDataGridHandle };\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Divider from \"@mui/material/Divider\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Typography from \"@mui/material/Typography\";\nimport {\n BarElement,\n CategoryScale,\n type ChartData,\n Chart as ChartJS,\n type ChartOptions,\n Tooltip as ChartTooltip,\n Legend,\n LinearScale,\n type Plugin,\n type ScriptableScaleContext,\n} from \"chart.js\";\nimport { Fragment, memo, useMemo } from \"react\";\nimport { Bar } from \"react-chartjs-2\";\nimport { getChartBarColors, getChartThemeColors } from \"./HistogramChart\";\n\n/** Rendered bar geometry — Chart.js plugin API types this as Element but bar datasets provide these fields at runtime */\ninterface RenderedBarGeometry {\n x: number;\n y: number;\n base: number;\n}\n\n// Register Chart.js modules once\nChartJS.register(CategoryScale, BarElement, LinearScale, Legend, ChartTooltip);\n\n/**\n * Single Top-K value item\n */\nexport interface TopKItem {\n /** Value label */\n label: string;\n /** Count */\n count: number;\n /** Whether this is a special label (null, empty, others) */\n isSpecial?: boolean;\n}\n\n/**\n * Top-K dataset for a single environment\n * Compatible with TopKResult from @datarecce/ui/api\n */\nexport interface TopKDataset {\n /** Value labels (null or undefined treated as special) */\n values: (string | number | null | undefined)[];\n /** Counts per value */\n counts: number[];\n /** Total valid count */\n valids: number;\n}\n\n/**\n * Props for single horizontal bar\n */\nexport interface SingleBarChartProps {\n /** Count value */\n count: number;\n /** Total count for percentage calculation */\n total: number;\n /** Bar color */\n color?: string;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Chart height in pixels */\n height?: number;\n}\n\n/**\n * Props for the TopKBarChart component\n */\nexport interface TopKBarChartProps {\n /** Base environment dataset */\n baseData?: TopKDataset;\n /** Current environment dataset */\n currentData: TopKDataset;\n /** Maximum items to display (default 10) */\n maxItems?: number;\n /** Show comparison with base */\n showComparison?: boolean;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Chart title */\n title?: string;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Props for TopKSummaryList component\n */\nexport interface TopKSummaryListProps {\n /** Top-K dataset */\n data: TopKDataset;\n /** Maximum items to display */\n maxItems?: number;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Format number as abbreviated (K, M, B, T)\n */\nfunction formatAbbreviated(value: number): string {\n if (value >= 1e12) return `${(value / 1e12).toFixed(1)}T`;\n if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;\n if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;\n if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;\n return String(value);\n}\n\n/**\n * Format as percentage\n */\nfunction formatPercent(value: number): string {\n if (value > 0 && value <= 0.001) return \"<0.1%\";\n if (value < 1 && value >= 0.999) return \">99.9%\";\n return `${(value * 100).toFixed(1)}%`;\n}\n\n/**\n * Prepare summary list from dataset\n */\nfunction prepareSummaryList(\n dataset: TopKDataset,\n maxItems: number,\n): TopKItem[] {\n const endAt = Math.min(maxItems, dataset.counts.length);\n const displayedCounts = dataset.counts.slice(0, endAt);\n const displayedSum = displayedCounts.reduce((a, b) => a + b, 0);\n const remainingCount = dataset.valids - displayedSum;\n\n const items: TopKItem[] = displayedCounts.map((count, index) => {\n const value = dataset.values[index];\n let label: string;\n let isSpecial = false;\n\n if (value === null || value === undefined) {\n label = \"(null)\";\n isSpecial = true;\n } else if (typeof value === \"string\" && value.length === 0) {\n label = \"(empty)\";\n isSpecial = true;\n } else {\n label = String(value);\n }\n\n return { label, count, isSpecial };\n });\n\n // Add \"others\" if there's remaining count\n if (remainingCount > 0) {\n items.push({\n label: \"(others)\",\n count: remainingCount,\n isSpecial: true,\n });\n }\n\n return items;\n}\n\n/**\n * SingleBarChart Component\n *\n * A horizontal progress bar for displaying a single value.\n */\nfunction SingleBarChartComponent({\n count,\n total,\n color,\n theme = \"light\",\n height = 16,\n}: SingleBarChartProps) {\n const isDark = theme === \"dark\";\n const themeColors = getChartThemeColors(isDark);\n const barColors = getChartBarColors(isDark);\n const barColor = color ?? barColors.current;\n\n const chartData = useMemo<ChartData<\"bar\">>(\n () => ({\n labels: [\"\"],\n datasets: [\n {\n indexAxis: \"y\" as const,\n data: [count],\n backgroundColor: barColor,\n hoverBackgroundColor: barColor,\n borderWidth: 0,\n borderColor: barColor,\n barPercentage: 1,\n categoryPercentage: 0.6,\n },\n ],\n }),\n [count, barColor],\n );\n\n const chartOptions = useMemo<ChartOptions<\"bar\">>(\n () => ({\n responsive: true,\n maintainAspectRatio: false,\n indexAxis: \"y\" as const,\n scales: {\n x: {\n display: false,\n max: total,\n grid: { display: false },\n ticks: { color: themeColors.textColor },\n },\n y: {\n display: false,\n ticks: { color: themeColors.textColor },\n },\n },\n plugins: {\n tooltip: { enabled: false },\n },\n animation: false,\n }),\n [total, themeColors],\n );\n\n return (\n <div style={{ height, width: \"100%\" }}>\n <Bar data={chartData} options={chartOptions} />\n </div>\n );\n}\n\nexport const SingleBarChart = memo(SingleBarChartComponent);\nSingleBarChart.displayName = \"SingleBarChart\";\n\n/**\n * TopKSummaryList Component\n *\n * A list displaying top-k values with horizontal bar charts.\n *\n * @example Basic usage\n * ```tsx\n * import { TopKSummaryList } from '@datarecce/ui/primitives';\n *\n * function ValueDistribution({ topKData }) {\n * return (\n * <TopKSummaryList\n * data={topKData}\n * maxItems={10}\n * />\n * );\n * }\n * ```\n */\nfunction TopKSummaryListComponent({\n data,\n maxItems = 10,\n theme = \"light\",\n className,\n}: TopKSummaryListProps) {\n const isDark = theme === \"dark\";\n const barColors = getChartBarColors(isDark);\n const items = useMemo(\n () => prepareSummaryList(data, maxItems),\n [data, maxItems],\n );\n\n return (\n <Box className={className} sx={{ width: \"100%\" }}>\n {items.map((item) => (\n <Fragment key={item.label}>\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n width: \"100%\",\n \"&:hover\": { bgcolor: \"action.hover\" },\n px: 1.5,\n }}\n >\n <Tooltip title={item.label} placement=\"top-start\">\n <Typography\n sx={{\n width: \"14em\",\n fontSize: \"0.875rem\",\n color: item.isSpecial ? \"grey.400\" : \"inherit\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {item.label}\n </Typography>\n </Tooltip>\n <Box sx={{ display: \"flex\", height: \"2em\", width: \"10em\" }}>\n <SingleBarChart\n count={item.count}\n total={data.valids}\n color={barColors.current}\n theme={theme}\n />\n </Box>\n <Tooltip title={String(item.count)} placement=\"top-start\">\n <Typography\n sx={{\n ml: 2.5,\n mr: 1,\n fontSize: \"0.875rem\",\n width: \"4em\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {formatAbbreviated(item.count)}\n </Typography>\n </Tooltip>\n <Typography\n sx={{\n color: \"grey.400\",\n fontSize: \"0.875rem\",\n width: \"4em\",\n }}\n >\n {formatPercent(item.count / data.valids)}\n </Typography>\n </Box>\n <Divider />\n </Fragment>\n ))}\n </Box>\n );\n}\n\nexport const TopKSummaryList = memo(TopKSummaryListComponent);\nTopKSummaryList.displayName = \"TopKSummaryList\";\n\n/**\n * TopKBarChart Component\n *\n * A pure presentation component for displaying top-k value distributions\n * with optional base/current comparison.\n *\n * @example Basic usage\n * ```tsx\n * import { TopKBarChart } from '@datarecce/ui/primitives';\n *\n * function ValueDistribution({ topKData }) {\n * return (\n * <TopKBarChart\n * currentData={topKData}\n * maxItems={10}\n * />\n * );\n * }\n * ```\n *\n * @example With comparison\n * ```tsx\n * <TopKBarChart\n * baseData={baseTopK}\n * currentData={currentTopK}\n * showComparison\n * maxItems={10}\n * />\n * ```\n */\nfunction TopKBarChartComponent({\n baseData,\n currentData,\n maxItems = 10,\n showComparison = false,\n theme = \"light\",\n title,\n className,\n}: TopKBarChartProps) {\n const isDark = theme === \"dark\";\n const barColors = getChartBarColors(isDark);\n const themeColors = getChartThemeColors(isDark);\n\n const currentItems = useMemo(\n () => prepareSummaryList(currentData, maxItems),\n [currentData, maxItems],\n );\n\n const baseItems = useMemo(\n () => (baseData ? prepareSummaryList(baseData, maxItems) : []),\n [baseData, maxItems],\n );\n\n const showBase = showComparison && baseData && baseItems.length > 0;\n\n const currentTotal = currentData.valids || 1;\n const baseTotal = baseData?.valids || 1;\n\n // Build display items, filtering empty \"(others)\" rows\n const displayItems = useMemo(() => {\n return currentItems\n .map((current, index) => ({\n current,\n base: showBase ? (baseItems[index] ?? null) : null,\n }))\n .filter(\n ({ current, base }) =>\n !(\n current.label === \"(others)\" &&\n current.count === 0 &&\n (!base || base.count === 0)\n ),\n );\n }, [currentItems, baseItems, showBase]);\n\n // Normalize counts to proportions so bars represent distribution, not absolute volume.\n // This ensures identical distributions produce identical bar lengths regardless of scale.\n const chartData = useMemo<ChartData<\"bar\">>(() => {\n const labels = displayItems.map(({ current }) => current.label);\n\n const datasets: ChartData<\"bar\">[\"datasets\"] = [\n {\n label: \"Current\",\n data: displayItems.map(({ current }) => current.count / currentTotal),\n backgroundColor: barColors.current,\n hoverBackgroundColor: barColors.current,\n borderWidth: 0,\n borderRadius: 3,\n barPercentage: showBase ? 0.9 : 1,\n categoryPercentage: showBase ? 0.75 : 0.6,\n },\n ];\n\n if (showBase) {\n datasets.push({\n label: \"Base\",\n data: displayItems.map(({ base }) => (base?.count ?? 0) / baseTotal),\n backgroundColor: barColors.base,\n hoverBackgroundColor: barColors.base,\n borderWidth: 0,\n borderRadius: 3,\n barPercentage: 0.9,\n categoryPercentage: 0.75,\n });\n }\n\n return { labels, datasets };\n }, [displayItems, barColors, showBase, currentTotal, baseTotal]);\n\n const chartOptions = useMemo<ChartOptions<\"bar\">>(\n () => ({\n responsive: true,\n maintainAspectRatio: false,\n indexAxis: \"y\" as const,\n layout: { padding: { right: 80 } },\n scales: {\n x: {\n display: false,\n grid: { display: false },\n },\n y: {\n grid: { display: false },\n ticks: {\n padding: 8,\n color: (ctx: ScriptableScaleContext) => {\n const item = displayItems[ctx.index];\n return item?.current.isSpecial\n ? \"#9ca3af\"\n : themeColors.textColor;\n },\n },\n },\n },\n plugins: {\n legend: {\n display: !!showBase,\n position: \"top\" as const,\n align: \"center\" as const,\n reverse: true,\n labels: {\n boxWidth: 32,\n boxHeight: 12,\n borderRadius: 3,\n useBorderRadius: true,\n color: themeColors.textColor,\n },\n },\n tooltip: {\n mode: \"index\" as const,\n callbacks: {\n label: (context) => {\n const proportion = context.parsed.x ?? 0;\n const total =\n context.dataset.label === \"Base\" ? baseTotal : currentTotal;\n const count = Math.round(proportion * total);\n return `${context.dataset.label}: ${formatAbbreviated(count)} (${formatPercent(proportion)})`;\n },\n },\n },\n },\n animation: false,\n }),\n [displayItems, themeColors, showBase, baseTotal, currentTotal],\n );\n\n const secondaryTextColor = isDark ? \"#9ca3af\" : \"#6b7280\";\n\n const barLabelsPlugin = useMemo<Plugin<\"bar\">>(\n () => ({\n id: \"barLabels\",\n afterDatasetsDraw(chart) {\n const { ctx } = chart;\n ctx.save();\n ctx.font = \"11px system-ui, sans-serif\";\n ctx.textBaseline = \"middle\";\n const pad = 4;\n\n for (let dsIndex = 0; dsIndex < chart.data.datasets.length; dsIndex++) {\n const dataset = chart.data.datasets[dsIndex];\n const total = dataset.label === \"Base\" ? baseTotal : currentTotal;\n const meta = chart.getDatasetMeta(dsIndex);\n\n for (let i = 0; i < meta.data.length; i++) {\n const { x, y, base } = meta.data[\n i\n ] as unknown as RenderedBarGeometry;\n const proportion = (dataset.data[i] as number) ?? 0;\n if (proportion === 0) continue;\n const count = Math.round(proportion * total);\n\n const barWidth = x - base;\n const countText = formatAbbreviated(count);\n const pctText = formatPercent(proportion);\n const countWidth = ctx.measureText(countText).width;\n const fitsInside = countWidth + 2 * pad < barWidth;\n\n if (fitsInside) {\n ctx.fillStyle = themeColors.barLabelColor;\n ctx.textAlign = \"left\";\n ctx.fillText(countText, base + pad, y);\n ctx.fillStyle = secondaryTextColor;\n ctx.textAlign = \"left\";\n ctx.fillText(pctText, x + pad, y);\n } else {\n ctx.fillStyle = themeColors.textColor;\n ctx.textAlign = \"left\";\n ctx.fillText(countText, x + pad, y);\n ctx.fillStyle = secondaryTextColor;\n ctx.fillText(pctText, x + pad + countWidth + pad, y);\n }\n }\n }\n\n ctx.restore();\n },\n }),\n [baseTotal, currentTotal, themeColors, secondaryTextColor],\n );\n\n const chartHeight =\n displayItems.length * (showBase ? 46 : 32) + (showBase ? 30 : 0);\n\n return (\n <Box className={className} sx={{ width: \"100%\", px: 2, py: 2 }}>\n {title && (\n <Typography variant=\"subtitle1\" sx={{ fontWeight: 500, mb: 1 }}>\n {title}\n </Typography>\n )}\n <div style={{ height: Math.max(chartHeight, 50) }}>\n <Bar\n data={chartData}\n options={chartOptions}\n plugins={[barLabelsPlugin]}\n />\n </div>\n </Box>\n );\n}\n\nexport const TopKBarChart = memo(TopKBarChartComponent);\nTopKBarChart.displayName = \"TopKBarChart\";\n","\"use client\";\n\nimport { sql } from \"@codemirror/lang-sql\";\nimport { yaml } from \"@codemirror/lang-yaml\";\nimport { MergeView, unifiedMergeView } from \"@codemirror/merge\";\nimport { EditorState, type Extension } from \"@codemirror/state\";\nimport { EditorView, lineNumbers } from \"@codemirror/view\";\nimport Box from \"@mui/material/Box\";\nimport { memo, useEffect, useRef } from \"react\";\n\n/**\n * Supported languages for the diff editor\n */\nexport type DiffEditorLanguage = \"sql\" | \"yaml\" | \"text\";\n\n/**\n * Theme options for the diff editor\n */\nexport type DiffEditorTheme = \"light\" | \"dark\";\n\n/**\n * Props for the DiffEditor component\n */\nexport interface DiffEditorProps {\n /** Original (base) text content */\n original: string;\n /** Modified (current) text content */\n modified: string;\n /** Language for syntax highlighting */\n language?: DiffEditorLanguage;\n /** Whether editor is read-only */\n readOnly?: boolean;\n /** Show line numbers */\n lineNumbers?: boolean;\n /** Side-by-side view (true) or unified view (false) */\n sideBySide?: boolean;\n /** Editor height */\n height?: string;\n /** Theme mode */\n theme?: DiffEditorTheme;\n /** Callback when modified content changes */\n onModifiedChange?: (value: string) => void;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * Get language extension for CodeMirror\n */\nfunction getLanguageExtension(language: DiffEditorLanguage): Extension | null {\n switch (language) {\n case \"sql\":\n return sql();\n case \"yaml\":\n return yaml();\n default:\n return null;\n }\n}\n\n/**\n * Get theme extensions for CodeMirror\n */\nfunction getThemeExtensions(isDark: boolean): Extension[] {\n const baseTheme = EditorView.theme(\n {\n \"&\": {\n backgroundColor: isDark ? \"#1e1e1e\" : \"#ffffff\",\n color: isDark ? \"#d4d4d4\" : \"#1f2937\",\n },\n \".cm-content\": {\n caretColor: isDark ? \"#d4d4d4\" : \"#1f2937\",\n fontFamily: \"'JetBrains Mono', 'Fira Code', monospace\",\n fontSize: \"13px\",\n },\n \".cm-gutters\": {\n backgroundColor: isDark ? \"#252526\" : \"#f5f5f5\",\n color: isDark ? \"#858585\" : \"#6b7280\",\n border: \"none\",\n },\n \".cm-activeLineGutter\": {\n backgroundColor: isDark ? \"#2a2d2e\" : \"#e5e7eb\",\n },\n \".cm-activeLine\": {\n backgroundColor: isDark ? \"#2a2d2e40\" : \"#f3f4f640\",\n },\n // Merge view specific styles\n \".cm-changedLine\": {\n backgroundColor: isDark ? \"#3d3d0050\" : \"#fff3c550\",\n },\n \".cm-changedText\": {\n backgroundColor: isDark ? \"#5c5c0080\" : \"#fef08a80\",\n },\n \".cm-deletedChunk\": {\n backgroundColor: isDark ? \"#5c1f1f50\" : \"#ffc5c550\",\n },\n \".cm-insertedChunk\": {\n backgroundColor: isDark ? \"#1a4d1a50\" : \"#cefece50\",\n },\n },\n { dark: isDark },\n );\n\n return [baseTheme];\n}\n\n/**\n * DiffEditor Component\n *\n * A pure presentation component for displaying text diffs using CodeMirror's\n * merge view. Supports side-by-side and unified diff views.\n *\n * @example Basic usage\n * ```tsx\n * import { DiffEditor } from '@datarecce/ui/primitives';\n *\n * function SqlDiffPanel({ baseSql, currentSql }) {\n * return (\n * <DiffEditor\n * original={baseSql}\n * modified={currentSql}\n * language=\"sql\"\n * sideBySide\n * />\n * );\n * }\n * ```\n *\n * @example Unified view with editing\n * ```tsx\n * const [modifiedSql, setModifiedSql] = useState(currentSql);\n *\n * <DiffEditor\n * original={baseSql}\n * modified={modifiedSql}\n * language=\"sql\"\n * sideBySide={false}\n * onModifiedChange={setModifiedSql}\n * />\n * ```\n */\nfunction DiffEditorComponent({\n original,\n modified,\n language = \"text\",\n readOnly = true,\n lineNumbers: showLineNumbers = true,\n sideBySide = true,\n height = \"400px\",\n theme = \"light\",\n onModifiedChange,\n className,\n}: DiffEditorProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const viewRef = useRef<MergeView | EditorView | null>(null);\n\n const isDark = theme === \"dark\";\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n // Clear previous view\n if (viewRef.current) {\n if (viewRef.current instanceof MergeView) {\n viewRef.current.destroy();\n } else {\n viewRef.current.destroy();\n }\n viewRef.current = null;\n }\n\n // Build extensions\n const extensions: Extension[] = [...getThemeExtensions(isDark)];\n\n if (showLineNumbers) {\n extensions.push(lineNumbers());\n }\n\n const langExt = getLanguageExtension(language);\n if (langExt) {\n extensions.push(langExt);\n }\n\n if (readOnly) {\n extensions.push(EditorState.readOnly.of(true));\n }\n\n // Create update listener if callback provided\n if (onModifiedChange && !readOnly) {\n extensions.push(\n EditorView.updateListener.of((update) => {\n if (update.docChanged) {\n onModifiedChange(update.state.doc.toString());\n }\n }),\n );\n }\n\n if (sideBySide) {\n // Side-by-side merge view\n // Note: revertControls is intentionally omitted - MergeView shows no\n // buttons when undefined (unlike unifiedMergeView which defaults to true)\n const mergeView = new MergeView({\n a: {\n doc: original,\n extensions: [...extensions],\n },\n b: {\n doc: modified,\n extensions: [...extensions],\n },\n parent: containerRef.current,\n orientation: \"a-b\",\n highlightChanges: true,\n gutter: true,\n collapseUnchanged: { margin: 3, minSize: 4 },\n });\n\n viewRef.current = mergeView;\n } else {\n // Unified diff view\n const unifiedExtensions = [\n ...extensions,\n unifiedMergeView({\n original,\n highlightChanges: true,\n gutter: true,\n // Disable accept/reject buttons - this is a read-only diff view\n mergeControls: false,\n collapseUnchanged: { margin: 3, minSize: 4 },\n }),\n ];\n\n const view = new EditorView({\n state: EditorState.create({\n doc: modified,\n extensions: unifiedExtensions,\n }),\n parent: containerRef.current,\n });\n\n viewRef.current = view;\n }\n\n return () => {\n if (viewRef.current) {\n if (viewRef.current instanceof MergeView) {\n viewRef.current.destroy();\n } else {\n viewRef.current.destroy();\n }\n viewRef.current = null;\n }\n };\n }, [\n original,\n modified,\n language,\n readOnly,\n showLineNumbers,\n sideBySide,\n isDark,\n onModifiedChange,\n ]);\n\n return (\n <Box\n ref={containerRef}\n className={className}\n sx={{\n height,\n width: \"100%\",\n overflow: \"auto\",\n border: \"1px solid\",\n borderColor: isDark ? \"grey.700\" : \"grey.300\",\n borderRadius: 1,\n \"& .cm-editor\": {\n height: \"100%\",\n },\n \"& .cm-scroller\": {\n overflow: \"auto\",\n },\n // Merge view layout\n \"& .cm-merge-view\": {\n height: \"100%\",\n },\n \"& .cm-merge-view > div\": {\n height: \"100%\",\n },\n }}\n />\n );\n}\n\nexport const DiffEditor = memo(DiffEditorComponent);\nDiffEditor.displayName = \"DiffEditor\";\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Typography from \"@mui/material/Typography\";\nimport { memo, type ReactNode } from \"react\";\n\n/**\n * Props for the EmptyState component\n */\nexport interface EmptyStateProps {\n /** Main title text */\n title: string;\n /** Description text */\n description?: string;\n /** Icon to display */\n icon?: ReactNode;\n /** Primary action button text */\n actionLabel?: string;\n /** Primary action callback */\n onAction?: () => void;\n /** Secondary action button text */\n secondaryActionLabel?: string;\n /** Secondary action callback */\n onSecondaryAction?: () => void;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Vertical padding */\n paddingY?: number;\n /** Optional CSS class */\n className?: string;\n /** Additional content below actions */\n children?: ReactNode;\n}\n\n/**\n * EmptyState Component\n *\n * A pure presentation component for displaying empty states\n * with optional icon, actions, and custom content.\n *\n * @example Basic usage\n * ```tsx\n * import { EmptyState } from '@datarecce/ui/primitives';\n *\n * function ChecksPanel({ checks }) {\n * if (checks.length === 0) {\n * return (\n * <EmptyState\n * title=\"No checks yet\"\n * description=\"Create your first check to get started\"\n * />\n * );\n * }\n * // ... render checks\n * }\n * ```\n *\n * @example With action button\n * ```tsx\n * <EmptyState\n * title=\"No results found\"\n * description=\"Try adjusting your search criteria\"\n * actionLabel=\"Clear Filters\"\n * onAction={() => clearFilters()}\n * />\n * ```\n *\n * @example With icon and multiple actions\n * ```tsx\n * <EmptyState\n * icon={<FolderIcon />}\n * title=\"No files\"\n * description=\"Upload files to get started\"\n * actionLabel=\"Upload File\"\n * onAction={handleUpload}\n * secondaryActionLabel=\"Learn More\"\n * onSecondaryAction={() => window.open(docsUrl)}\n * />\n * ```\n */\nfunction EmptyStateComponent({\n title,\n description,\n icon,\n actionLabel,\n onAction,\n secondaryActionLabel,\n onSecondaryAction,\n theme = \"light\",\n paddingY = 8,\n className,\n children,\n}: EmptyStateProps) {\n const isDark = theme === \"dark\";\n\n return (\n <Box\n className={className}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n textAlign: \"center\",\n py: paddingY,\n px: 4,\n height: \"100%\",\n minHeight: 200,\n }}\n >\n {/* Icon */}\n {icon && (\n <Box\n sx={{\n mb: 2,\n color: isDark ? \"grey.500\" : \"grey.400\",\n \"& svg\": {\n width: 48,\n height: 48,\n },\n }}\n >\n {icon}\n </Box>\n )}\n\n {/* Title */}\n <Typography\n variant=\"h6\"\n sx={{\n fontWeight: 500,\n color: isDark ? \"grey.300\" : \"grey.700\",\n mb: description ? 1 : 0,\n }}\n >\n {title}\n </Typography>\n\n {/* Description */}\n {description && (\n <Typography\n variant=\"body2\"\n sx={{\n color: isDark ? \"grey.400\" : \"grey.500\",\n maxWidth: 400,\n mb: actionLabel || secondaryActionLabel ? 3 : 0,\n }}\n >\n {description}\n </Typography>\n )}\n\n {/* Actions */}\n {(actionLabel || secondaryActionLabel) && (\n <Box sx={{ display: \"flex\", gap: 2, mt: 1 }}>\n {actionLabel && onAction && (\n <Button variant=\"contained\" onClick={onAction} size=\"small\">\n {actionLabel}\n </Button>\n )}\n {secondaryActionLabel && onSecondaryAction && (\n <Button variant=\"outlined\" onClick={onSecondaryAction} size=\"small\">\n {secondaryActionLabel}\n </Button>\n )}\n </Box>\n )}\n\n {/* Additional content */}\n {children && <Box sx={{ mt: 3 }}>{children}</Box>}\n </Box>\n );\n}\n\nexport const EmptyState = memo(EmptyStateComponent);\nEmptyState.displayName = \"EmptyState\";\n","\"use client\";\n\n/**\n * ExternalLinkConfirmDialog - Confirmation dialog for external links.\n *\n * Shows a warning when users click on links that navigate outside of Recce.\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Typography from \"@mui/material/Typography\";\nimport { useRef } from \"react\";\nimport { IoClose } from \"react-icons/io5\";\nimport { PiWarning } from \"react-icons/pi\";\n\n/**\n * Props for ExternalLinkConfirmDialog\n */\nexport interface ExternalLinkConfirmDialogProps {\n /** Whether the dialog is open */\n isOpen: boolean;\n /** The external URL the user is trying to navigate to */\n url: string;\n /** Callback when user confirms navigation */\n onConfirm: () => void;\n /** Callback when user cancels navigation */\n onCancel: () => void;\n}\n\n/**\n * Truncate a URL for display, keeping the domain visible\n */\nexport function truncateUrl(url: string, maxLength = 60): string {\n if (url.length <= maxLength) return url;\n\n try {\n const urlObj = new URL(url);\n const domain = urlObj.hostname;\n const path = urlObj.pathname + urlObj.search + urlObj.hash;\n\n // Always show the domain\n if (domain.length >= maxLength - 3) {\n return domain.substring(0, maxLength - 3) + \"...\";\n }\n\n // Calculate remaining space for path\n const remainingLength = maxLength - domain.length - 3;\n if (path.length > remainingLength) {\n return `${domain}${path.substring(0, remainingLength)}...`;\n }\n\n return url;\n } catch {\n // If URL parsing fails, just truncate normally\n return url.substring(0, maxLength - 3) + \"...\";\n }\n}\n\n/**\n * ExternalLinkConfirmDialog Component\n *\n * A dialog that asks users to confirm before navigating to external URLs.\n *\n * @example Basic usage\n * ```tsx\n * import { ExternalLinkConfirmDialog } from '@datarecce/ui/primitives';\n *\n * function MyComponent() {\n * const [isOpen, setIsOpen] = useState(false);\n * const [pendingUrl, setPendingUrl] = useState('');\n *\n * return (\n * <ExternalLinkConfirmDialog\n * isOpen={isOpen}\n * url={pendingUrl}\n * onConfirm={() => {\n * window.open(pendingUrl, '_blank');\n * setIsOpen(false);\n * }}\n * onCancel={() => setIsOpen(false)}\n * />\n * );\n * }\n * ```\n */\nexport function ExternalLinkConfirmDialog({\n isOpen,\n url,\n onConfirm,\n onCancel,\n}: ExternalLinkConfirmDialogProps) {\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n return (\n <MuiDialog\n open={isOpen}\n onClose={onCancel}\n maxWidth=\"sm\"\n fullWidth\n aria-labelledby=\"external-link-dialog-title\"\n >\n <DialogTitle\n id=\"external-link-dialog-title\"\n sx={{ display: \"flex\", alignItems: \"center\", gap: 1 }}\n >\n <Box component={PiWarning} sx={{ color: \"amber.500\", fontSize: 20 }} />\n External Link\n </DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={onCancel}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n\n <DialogContent>\n <Typography sx={{ mb: 1.5 }}>\n This link will take you to an external website outside of Recce. Are\n you sure you want to continue?\n </Typography>\n <Box\n sx={{\n bgcolor: \"grey.50\",\n p: 1,\n borderRadius: 1,\n border: \"1px solid\",\n borderColor: \"grey.200\",\n }}\n >\n <Box\n component=\"code\"\n sx={{\n fontSize: \"0.875rem\",\n wordBreak: \"break-all\",\n whiteSpace: \"pre-wrap\",\n bgcolor: \"transparent\",\n fontFamily: \"monospace\",\n }}\n >\n {truncateUrl(url, 100)}\n </Box>\n </Box>\n </DialogContent>\n\n <DialogActions sx={{ gap: 1 }}>\n <Button ref={cancelRef} variant=\"outlined\" onClick={onCancel}>\n Cancel\n </Button>\n <Button color=\"iochmara\" variant=\"contained\" onClick={onConfirm}>\n Open Link\n </Button>\n </DialogActions>\n </MuiDialog>\n );\n}\n","\"use client\";\n\n/**\n * MarkdownContent - Renders GitHub-flavored Markdown content.\n *\n * Features:\n * - GFM support (tables, task lists, strikethrough, autolinks)\n * - Syntax highlighting for code blocks\n * - External link confirmation dialog\n * - XSS-safe rendering (no dangerouslySetInnerHTML)\n */\n\nimport Box from \"@mui/material/Box\";\nimport Link from \"@mui/material/Link\";\nimport Typography from \"@mui/material/Typography\";\nimport React, { useState } from \"react\";\nimport Markdown, { Components } from \"react-markdown\";\nimport { Prism as SyntaxHighlighter } from \"react-syntax-highlighter\";\nimport { oneDark } from \"react-syntax-highlighter/dist/esm/styles/prism\";\nimport remarkGfm from \"remark-gfm\";\nimport { useIsDark } from \"../../hooks\";\nimport { ExternalLinkConfirmDialog } from \"./ExternalLinkConfirmDialog\";\n\n/**\n * Props for MarkdownContent component\n */\nexport interface MarkdownContentProps {\n /** The markdown content to render */\n content: string;\n /** Font size for the rendered content */\n fontSize?: string;\n /** Additional domains to treat as internal (not showing confirmation) */\n internalDomains?: string[];\n}\n\n/**\n * Check if a URL is external (not part of the Recce application)\n */\nfunction isExternalUrl(href: string, internalDomains: string[]): boolean {\n if (!href) return false;\n\n // Relative URLs are internal\n if (href.startsWith(\"/\") || href.startsWith(\"#\") || href.startsWith(\"?\")) {\n return false;\n }\n\n // Check for protocol-relative or absolute URLs\n try {\n const url = new URL(href, window.location.origin);\n\n // Check if the hostname matches any internal domain\n return !internalDomains.some(\n (domain) =>\n url.hostname === domain || url.hostname.endsWith(`.${domain}`),\n );\n } catch {\n // If URL parsing fails, treat as internal (likely a relative path)\n return false;\n }\n}\n\n/**\n * Custom link component that shows confirmation for external links\n */\nfunction MarkdownLink({\n href,\n children,\n internalDomains,\n}: {\n href?: string;\n children?: React.ReactNode;\n internalDomains: string[];\n}) {\n const [showConfirm, setShowConfirm] = useState(false);\n const [pendingUrl, setPendingUrl] = useState<string | null>(null);\n\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\n if (!href) return;\n\n if (isExternalUrl(href, internalDomains)) {\n e.preventDefault();\n setPendingUrl(href);\n setShowConfirm(true);\n }\n };\n\n const handleConfirm = () => {\n if (pendingUrl) {\n window.open(pendingUrl, \"_blank\", \"noopener,noreferrer\");\n }\n setShowConfirm(false);\n setPendingUrl(null);\n };\n\n const handleCancel = () => {\n setShowConfirm(false);\n setPendingUrl(null);\n };\n\n const isExternal = href ? isExternalUrl(href, internalDomains) : false;\n\n return (\n <>\n <Link\n href={href}\n onClick={handleClick}\n sx={{\n color: \"primary.main\",\n textDecoration: \"underline\",\n \"&:hover\": { color: \"iochmara.600\" },\n }}\n target=\"_blank\"\n rel={isExternal ? \"noopener noreferrer\" : undefined}\n >\n {children}\n {isExternal && \" ↗\"}\n </Link>\n <ExternalLinkConfirmDialog\n isOpen={showConfirm}\n url={pendingUrl || \"\"}\n onConfirm={handleConfirm}\n onCancel={handleCancel}\n />\n </>\n );\n}\n\n/**\n * Custom code block component with syntax highlighting\n */\nfunction CodeBlock({\n className,\n children,\n isDark = false,\n}: {\n className?: string;\n children?: React.ReactNode;\n node?: unknown;\n isDark?: boolean;\n}) {\n const match = /language-(\\w+)/.exec(className || \"\");\n const language = match ? match[1] : undefined;\n const codeString = String(children).replace(/\\n$/, \"\");\n\n // Check if this is an inline code block (no language, single line, no newlines)\n const isInline = !match && !String(children).includes(\"\\n\");\n\n if (isInline) {\n return (\n <Box\n component=\"code\"\n sx={{\n bgcolor: isDark ? \"grey.800\" : \"grey.100\",\n color: isDark ? \"grey.200\" : \"inherit\",\n px: 1,\n py: 0.5,\n borderRadius: 0.5,\n fontSize: \"0.9em\",\n fontFamily: \"monospace\",\n }}\n >\n {children}\n </Box>\n );\n }\n\n return (\n <Box\n sx={{ my: 2, borderRadius: 1, overflow: \"hidden\", fontSize: \"0.875rem\" }}\n >\n <SyntaxHighlighter\n style={oneDark}\n language={language}\n PreTag=\"div\"\n customStyle={{\n margin: 0,\n borderRadius: \"6px\",\n fontSize: \"0.85em\",\n }}\n >\n {codeString}\n </SyntaxHighlighter>\n </Box>\n );\n}\n\n/**\n * MarkdownContent Component\n *\n * A component for rendering GitHub-flavored Markdown with syntax highlighting\n * and external link confirmation.\n *\n * @example Basic usage\n * ```tsx\n * import { MarkdownContent } from '@datarecce/ui/primitives';\n *\n * function Description({ text }) {\n * return <MarkdownContent content={text} />;\n * }\n * ```\n *\n * @example With custom font size\n * ```tsx\n * <MarkdownContent content={markdown} fontSize=\"1rem\" />\n * ```\n *\n * @example With additional internal domains\n * ```tsx\n * <MarkdownContent\n * content={markdown}\n * internalDomains={['company.com', 'docs.company.com']}\n * />\n * ```\n */\nexport function MarkdownContent({\n content,\n fontSize = \"0.875rem\",\n internalDomains = [],\n}: MarkdownContentProps) {\n const isDark = useIsDark();\n\n // Build the list of internal domains\n const allInternalDomains = [\n window.location.hostname,\n \"reccehq.com\",\n \"datarecce.io\",\n \"localhost\",\n ...internalDomains,\n ];\n\n // Custom component renderers\n const components: Components = {\n // Links with external confirmation\n a: ({ href, children }) => (\n <MarkdownLink href={href} internalDomains={allInternalDomains}>\n {children}\n </MarkdownLink>\n ),\n\n // Code blocks with syntax highlighting\n code: (props) => <CodeBlock {...props} isDark={isDark} />,\n\n // Paragraphs\n p: ({ children }) => (\n <Typography\n component=\"p\"\n sx={{ fontSize, mb: 2, \"&:last-child\": { mb: 0 } }}\n >\n {children}\n </Typography>\n ),\n\n // Headers\n h1: ({ children }) => (\n <Typography\n sx={{ fontSize: \"1.25rem\", fontWeight: \"bold\", mb: 2, mt: 3 }}\n >\n {children}\n </Typography>\n ),\n h2: ({ children }) => (\n <Typography\n sx={{ fontSize: \"1.125rem\", fontWeight: \"bold\", mb: 2, mt: 3 }}\n >\n {children}\n </Typography>\n ),\n h3: ({ children }) => (\n <Typography sx={{ fontSize: \"1rem\", fontWeight: 600, mb: 2, mt: 2 }}>\n {children}\n </Typography>\n ),\n\n // Lists\n ul: ({ children }) => (\n <Box component=\"ul\" sx={{ pl: 4, mb: 2, listStyleType: \"disc\" }}>\n {children}\n </Box>\n ),\n ol: ({ children }) => (\n <Box component=\"ol\" sx={{ pl: 4, mb: 2, listStyleType: \"decimal\" }}>\n {children}\n </Box>\n ),\n li: ({ children }) => (\n <Box component=\"li\" sx={{ fontSize, mb: 1 }}>\n {children}\n </Box>\n ),\n\n // Blockquotes\n blockquote: ({ children }) => (\n <Box\n sx={{\n borderLeft: \"3px solid\",\n borderLeftColor: isDark ? \"grey.600\" : \"grey.300\",\n pl: 3,\n py: 1,\n my: 2,\n color: isDark ? \"grey.400\" : \"grey.600\",\n fontStyle: \"italic\",\n }}\n >\n {children}\n </Box>\n ),\n\n // Tables\n table: ({ children }) => (\n <Box sx={{ overflowX: \"auto\", my: 2 }}>\n <Box\n component=\"table\"\n sx={{\n width: \"100%\",\n fontSize,\n border: \"1px solid\",\n borderColor: isDark ? \"grey.700\" : \"grey.200\",\n borderRadius: 1,\n }}\n >\n {children}\n </Box>\n </Box>\n ),\n thead: ({ children }) => (\n <Box component=\"thead\" sx={{ bgcolor: isDark ? \"grey.800\" : \"grey.50\" }}>\n {children}\n </Box>\n ),\n tbody: ({ children }) => <Box component=\"tbody\">{children}</Box>,\n tr: ({ children }) => (\n <Box\n component=\"tr\"\n sx={{\n borderBottom: \"1px solid\",\n borderColor: isDark ? \"grey.700\" : \"grey.200\",\n }}\n >\n {children}\n </Box>\n ),\n th: ({ children }) => (\n <Box\n component=\"th\"\n sx={{ px: 2, py: 1, fontWeight: 600, textAlign: \"left\" }}\n >\n {children}\n </Box>\n ),\n td: ({ children }) => (\n <Box component=\"td\" sx={{ px: 2, py: 1 }}>\n {children}\n </Box>\n ),\n\n // Horizontal rule\n hr: () => (\n <Box\n component=\"hr\"\n sx={{ my: 3, borderColor: isDark ? \"grey.700\" : \"grey.200\" }}\n />\n ),\n\n // Strong/Bold\n strong: ({ children }) => (\n <Typography component=\"strong\" sx={{ fontWeight: 600 }}>\n {children}\n </Typography>\n ),\n\n // Emphasis/Italic\n em: ({ children }) => (\n <Typography component=\"em\" sx={{ fontStyle: \"italic\" }}>\n {children}\n </Typography>\n ),\n\n // Strikethrough\n del: ({ children }) => (\n <Typography\n component=\"del\"\n sx={{ textDecoration: \"line-through\", color: \"grey.500\" }}\n >\n {children}\n </Typography>\n ),\n };\n\n return (\n <Box className=\"markdown-content\">\n <Markdown remarkPlugins={[remarkGfm]} components={components}>\n {content}\n </Markdown>\n </Box>\n );\n}\n","\"use client\";\n\n/**\n * @file ScreenshotBox.tsx\n * @description A wrapper component for content that can be captured as a screenshot.\n *\n * This component provides a ref-forwardable container that can be used with\n * html-to-image or similar libraries to capture its contents as an image.\n */\n\nimport type { BoxProps } from \"@mui/material/Box\";\nimport Box from \"@mui/material/Box\";\nimport { forwardRef, type Ref } from \"react\";\n\nexport interface ScreenshotBoxProps extends BoxProps {\n /** Background color for the screenshot area */\n backgroundColor?: string;\n /** Block size (height in block direction) */\n blockSize?: string;\n /** Content to render inside the screenshot area */\n children?: React.ReactNode;\n}\n\n/**\n * A container component that can be captured as a screenshot.\n *\n * The component forwards its ref to the outer container, allowing parent\n * components to capture the element using html-to-image or similar libraries.\n *\n * @example\n * ```tsx\n * const ref = useRef<HTMLDivElement>(null);\n *\n * const captureScreenshot = async () => {\n * if (ref.current) {\n * const dataUrl = await toPng(ref.current);\n * // Use dataUrl...\n * }\n * };\n *\n * return (\n * <ScreenshotBox ref={ref} backgroundColor=\"white\">\n * <Chart data={data} />\n * </ScreenshotBox>\n * );\n * ```\n */\nexport const ScreenshotBox = forwardRef(function ScreenshotBox(\n {\n backgroundColor = \"white\",\n blockSize,\n children,\n ...restProps\n }: ScreenshotBoxProps,\n ref: Ref<HTMLDivElement>,\n) {\n return (\n <Box\n ref={ref}\n {...restProps}\n sx={{ overflowY: \"auto\", overflowX: \"hidden\", ...restProps.sx }}\n >\n <Box\n sx={{\n backgroundColor,\n height: \"100%\",\n blockSize,\n }}\n >\n {children}\n </Box>\n </Box>\n );\n});\n","\"use client\";\n\nimport \"./splitStyles.css\";\nimport Box from \"@mui/material/Box\";\nimport { type CSSProperties, memo, type ReactNode } from \"react\";\nimport Split from \"react-split\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\n\n/**\n * Split direction\n */\nexport type SplitDirection = \"horizontal\" | \"vertical\";\n\n/**\n * Props for the SplitPane component\n */\nexport interface SplitPaneProps {\n /** Child elements to split */\n children: ReactNode;\n /** Split direction */\n direction?: SplitDirection;\n /** Initial sizes as percentages (should sum to 100) */\n sizes?: number[];\n /** Minimum sizes in pixels */\n minSizes?: number | number[];\n /** Maximum sizes in pixels */\n maxSizes?: number | number[];\n /** Gutter (drag handle) size in pixels */\n gutterSize?: number;\n /** Snap to closed at this threshold (pixels) */\n snapOffset?: number;\n /** Allow dragging past minSize to collapse */\n dragInterval?: number;\n /** Callback when sizes change */\n onDragEnd?: (sizes: number[]) => void;\n /** Callback during drag */\n onDrag?: (sizes: number[]) => void;\n /** Theme mode */\n theme?: \"light\" | \"dark\";\n /** Container style */\n style?: CSSProperties;\n /** Optional CSS class */\n className?: string;\n}\n\n/**\n * SplitPane Component\n *\n * A pure presentation component for creating resizable split panes\n * using react-split. Supports horizontal and vertical layouts.\n *\n * @example Horizontal split\n * ```tsx\n * import { SplitPane } from '@datarecce/ui/primitives';\n *\n * function TwoColumnLayout() {\n * return (\n * <SplitPane direction=\"horizontal\" sizes={[30, 70]}>\n * <div>Left Panel</div>\n * <div>Right Panel</div>\n * </SplitPane>\n * );\n * }\n * ```\n *\n * @example Vertical split with min sizes\n * ```tsx\n * <SplitPane\n * direction=\"vertical\"\n * sizes={[50, 50]}\n * minSizes={[100, 100]}\n * >\n * <div>Top Panel</div>\n * <div>Bottom Panel</div>\n * </SplitPane>\n * ```\n *\n * @example Three-way split with callbacks\n * ```tsx\n * <SplitPane\n * direction=\"horizontal\"\n * sizes={[25, 50, 25]}\n * onDragEnd={(sizes) => saveSizes(sizes)}\n * >\n * <div>Navigation</div>\n * <div>Content</div>\n * <div>Details</div>\n * </SplitPane>\n * ```\n */\nfunction SplitPaneComponent({\n children,\n direction = \"horizontal\",\n sizes,\n minSizes = 0,\n maxSizes,\n gutterSize = 5,\n snapOffset = 30,\n dragInterval = 1,\n onDragEnd,\n onDrag,\n theme,\n style,\n className,\n}: SplitPaneProps) {\n const isDarkAuto = useIsDark();\n const isDark = theme ? theme === \"dark\" : isDarkAuto;\n\n const containerStyle: CSSProperties = {\n display: \"flex\",\n flexDirection: direction === \"horizontal\" ? \"row\" : \"column\",\n height: \"100%\",\n width: \"100%\",\n ...style,\n };\n\n return (\n <Box\n className={className}\n sx={{\n height: \"100%\",\n width: \"100%\",\n \"& .gutter\": {\n backgroundColor: \"divider\",\n backgroundRepeat: \"no-repeat\",\n backgroundPosition: \"50%\",\n transition: \"background-color 0.15s ease\",\n \"&:hover\": {\n backgroundColor: isDark ? \"grey.600\" : \"grey.300\",\n },\n },\n \"& .gutter.gutter-horizontal\": {\n cursor: \"col-resize\",\n },\n \"& .gutter.gutter-vertical\": {\n cursor: \"row-resize\",\n },\n }}\n >\n <Split\n style={containerStyle}\n direction={direction}\n sizes={sizes}\n minSize={minSizes}\n maxSize={maxSizes}\n gutterSize={gutterSize}\n snapOffset={snapOffset}\n dragInterval={dragInterval}\n onDragEnd={onDragEnd}\n onDrag={onDrag}\n >\n {children}\n </Split>\n </Box>\n );\n}\n\nexport const SplitPane = memo(SplitPaneComponent);\nSplitPane.displayName = \"SplitPane\";\n","\"use client\";\n\nimport type { SplitProps as ReactSplitProps } from \"react-split\";\nimport { SplitPane } from \"./SplitPane\";\n\n/**\n * Props for HSplit and VSplit components\n *\n * These components provide backward-compatible wrappers around SplitPane,\n * accepting the same props as react-split's SplitProps.\n */\nexport type SplitProps = ReactSplitProps;\n\n/**\n * Horizontal Split Component\n *\n * A convenience wrapper around SplitPane that creates a horizontal (left-right) split.\n * Maintains backward compatibility with react-split's SplitProps interface.\n *\n * @example Basic horizontal split\n * ```tsx\n * import { HSplit } from '@datarecce/ui';\n *\n * function TwoColumnLayout() {\n * return (\n * <HSplit sizes={[30, 70]} minSize={100}>\n * <div>Left Panel</div>\n * <div>Right Panel</div>\n * </HSplit>\n * );\n * }\n * ```\n *\n * @example With custom styling\n * ```tsx\n * <HSplit\n * sizes={[20, 80]}\n * minSize={50}\n * style={{ height: \"100%\" }}\n * >\n * <nav>Navigation</nav>\n * <main>Content</main>\n * </HSplit>\n * ```\n */\nexport function HSplit(props: SplitProps) {\n const {\n style,\n children,\n gutterSize = 5,\n minSize,\n maxSize,\n sizes,\n snapOffset,\n dragInterval,\n onDragEnd,\n onDrag,\n className,\n } = props;\n\n return (\n <SplitPane\n direction=\"horizontal\"\n gutterSize={gutterSize}\n minSizes={minSize}\n maxSizes={maxSize}\n sizes={sizes}\n snapOffset={typeof snapOffset === \"number\" ? snapOffset : undefined}\n dragInterval={dragInterval}\n onDragEnd={onDragEnd}\n onDrag={onDrag}\n style={style}\n className={className}\n >\n {children}\n </SplitPane>\n );\n}\n\n/**\n * Vertical Split Component\n *\n * A convenience wrapper around SplitPane that creates a vertical (top-bottom) split.\n * Maintains backward compatibility with react-split's SplitProps interface.\n *\n * @example Basic vertical split\n * ```tsx\n * import { VSplit } from '@datarecce/ui';\n *\n * function TwoRowLayout() {\n * return (\n * <VSplit sizes={[60, 40]} minSize={100}>\n * <div>Top Panel</div>\n * <div>Bottom Panel</div>\n * </VSplit>\n * );\n * }\n * ```\n *\n * @example Nested splits\n * ```tsx\n * <HSplit sizes={[30, 70]}>\n * <div>Sidebar</div>\n * <VSplit sizes={[70, 30]}>\n * <div>Main Content</div>\n * <div>Footer</div>\n * </VSplit>\n * </HSplit>\n * ```\n */\nexport function VSplit(props: SplitProps) {\n const {\n style,\n children,\n gutterSize = 5,\n minSize,\n maxSize,\n sizes,\n snapOffset,\n dragInterval,\n onDragEnd,\n onDrag,\n className,\n } = props;\n\n return (\n <SplitPane\n direction=\"vertical\"\n gutterSize={gutterSize}\n minSizes={minSize}\n maxSizes={maxSize}\n sizes={sizes}\n snapOffset={typeof snapOffset === \"number\" ? snapOffset : undefined}\n dragInterval={dragInterval}\n onDragEnd={onDragEnd}\n onDrag={onDrag}\n style={style}\n className={className}\n >\n {children}\n </SplitPane>\n );\n}\n","\"use client\";\n\nimport Alert from \"@mui/material/Alert\";\nimport Box from \"@mui/material/Box\";\nimport { amber } from \"@mui/material/colors\";\nimport { forwardRef, type ReactNode, type Ref, useMemo } from \"react\";\nimport { PiWarning } from \"react-icons/pi\";\n\nimport { useIsDark } from \"../../hooks\";\nimport {\n type DataGridHandle,\n EmptyRowsRenderer,\n ScreenshotDataGrid,\n} from \"../data/ScreenshotDataGrid\";\nimport { ScreenshotBox } from \"../ui/ScreenshotBox\";\nimport type {\n CreatedResultViewProps,\n ResultViewConfig,\n ResultViewRef,\n WarningStyle,\n} from \"./types\";\n\n/**\n * Renders a single warning with amber styling (icon + text).\n */\nfunction AmberWarning({\n warning,\n isDark,\n}: {\n warning: string;\n isDark: boolean;\n}) {\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 0.5,\n fontSize: \"0.75rem\",\n }}\n >\n <PiWarning color={isDark ? amber[400] : amber[600]} />\n <Box>{warning}</Box>\n </Box>\n );\n}\n\n/**\n * Toolbar area component for ResultView.\n * Renders warnings on the left, spacer, and toolbar controls on the right.\n */\nfunction ToolbarArea({\n toolbar,\n warnings,\n warningStyle = \"alert\",\n isDark,\n}: {\n toolbar?: ReactNode;\n warnings?: string[];\n warningStyle?: WarningStyle;\n isDark: boolean;\n}) {\n if (!toolbar && (!warnings || warnings.length === 0)) {\n return null;\n }\n\n // Determine background color based on warning style\n const bgColor =\n warningStyle === \"amber\" && warnings && warnings.length > 0\n ? isDark\n ? amber[900]\n : amber[100]\n : isDark\n ? \"grey.900\"\n : \"grey.50\";\n\n // Determine text color for amber style\n const textColor =\n warningStyle === \"amber\" && warnings && warnings.length > 0\n ? isDark\n ? amber[200]\n : amber[800]\n : undefined;\n\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 1,\n px: 1,\n py: 0.5,\n borderBottom: 1,\n borderColor: \"divider\",\n bgcolor: bgColor,\n color: textColor,\n }}\n >\n {warningStyle === \"amber\"\n ? warnings?.map((warning) => (\n <AmberWarning key={warning} warning={warning} isDark={isDark} />\n ))\n : warnings?.map((warning) => (\n <Alert\n key={warning}\n severity=\"warning\"\n sx={{ py: 0, fontSize: \"0.75rem\" }}\n >\n {warning}\n </Alert>\n ))}\n <Box sx={{ flex: 1 }} />\n {toolbar}\n </Box>\n );\n}\n\n/**\n * Factory function to create type-safe ResultView components.\n *\n * @remarks\n * Reduces boilerplate by handling:\n * - Type guard validation with consistent error messages\n * - forwardRef setup for screenshot capture\n * - Dark/light theme handling\n * - Empty state rendering\n *\n * @typeParam TRun - Run payload type validated by the type guard.\n * @typeParam TViewOptions - Optional view options shape used by the view.\n * @typeParam TRef - Ref type exposed by the view (defaults to DataGridHandle).\n *\n * @example\n * ```tsx\n * export const RowCountResultView = createResultView({\n * displayName: \"RowCountResultView\",\n * typeGuard: isRowCountRun,\n * expectedRunType: \"row_count\",\n * screenshotWrapper: \"grid\",\n * transformData: (run) => ({\n * columns: toRowCountGrid(run).columns,\n * rows: toRowCountGrid(run).rows,\n * }),\n * });\n * ```\n */\nexport function createResultView<\n TRun,\n TViewOptions = unknown,\n TRef extends ResultViewRef = DataGridHandle,\n>(config: ResultViewConfig<TRun, TViewOptions>) {\n const {\n displayName,\n typeGuard,\n expectedRunType,\n screenshotWrapper,\n transformData,\n emptyState = \"No data\",\n conditionalEmptyState,\n } = config;\n\n function ResultViewInner(\n {\n run,\n viewOptions,\n onViewOptionsChanged,\n onAddToChecklist,\n }: CreatedResultViewProps<TViewOptions>,\n ref: Ref<TRef>,\n ) {\n const isDark = useIsDark();\n\n // Type guard validation\n if (!typeGuard(run)) {\n throw new Error(`Run type must be ${expectedRunType}`);\n }\n\n // Transform data - memoized for performance (must be called before conditional returns)\n const data = useMemo(\n () =>\n transformData(run, {\n viewOptions,\n onViewOptionsChanged,\n onAddToChecklist,\n }),\n [run, viewOptions, onViewOptionsChanged, onAddToChecklist],\n );\n\n // Check conditional empty state\n const conditionalEmpty = conditionalEmptyState?.(run, viewOptions);\n if (conditionalEmpty !== null && conditionalEmpty !== undefined) {\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n height: \"100%\",\n }}\n >\n {conditionalEmpty}\n </Box>\n );\n }\n\n // Return null case (component renders nothing)\n if (data?.renderNull) {\n return null;\n }\n\n // Empty state\n if (!data || data.isEmpty) {\n const hasToolbar =\n data?.toolbar || (data?.warnings && data.warnings.length > 0);\n\n // Empty state WITH toolbar (for patterns like ValueDiffDetailResultView \"No change\")\n if (hasToolbar) {\n return (\n <Box\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n height: \"100%\",\n }}\n >\n <ToolbarArea\n toolbar={data?.toolbar}\n warnings={data?.warnings}\n warningStyle={data?.warningStyle}\n isDark={isDark}\n />\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flex: 1,\n }}\n >\n {data?.emptyMessage ?? emptyState}\n </Box>\n </Box>\n );\n }\n\n // Empty state WITHOUT toolbar (default)\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n height: \"100%\",\n }}\n >\n {emptyState}\n </Box>\n );\n }\n\n // Render based on wrapper type\n if (screenshotWrapper === \"grid\") {\n return (\n <Box sx={{ display: \"flex\", flexDirection: \"column\", height: \"100%\" }}>\n {data.header}\n <ToolbarArea\n toolbar={data.toolbar}\n warnings={data.warnings}\n warningStyle={data.warningStyle}\n isDark={isDark}\n />\n <ScreenshotDataGrid\n ref={ref as Ref<DataGridHandle>}\n style={{\n blockSize: \"auto\",\n maxHeight: \"100%\",\n overflow: \"auto\",\n fontSize: \"0.875rem\",\n borderWidth: 1,\n }}\n columns={(data.columns ?? []) as never}\n rows={(data.rows ?? []) as never}\n renderers={{\n noRowsFallback: (\n <EmptyRowsRenderer emptyMessage={data.noRowsMessage} />\n ),\n }}\n defaultColumnOptions={data.defaultColumnOptions}\n />\n {data.footer}\n </Box>\n );\n }\n\n // Box wrapper for charts\n return (\n <Box sx={{ display: \"flex\", flexDirection: \"column\", height: \"100%\" }}>\n {data.header}\n <ToolbarArea\n toolbar={data.toolbar}\n warnings={data.warnings}\n warningStyle={data.warningStyle}\n isDark={isDark}\n />\n <ScreenshotBox\n ref={ref as Ref<HTMLDivElement>}\n height=\"100%\"\n backgroundColor={isDark ? \"#1f2937\" : \"white\"}\n >\n {data.content}\n </ScreenshotBox>\n {data.footer}\n </Box>\n );\n }\n\n // Set display name for DevTools\n ResultViewInner.displayName = displayName;\n\n // Create forwardRef component with proper typing\n const ForwardedResultView = forwardRef(ResultViewInner);\n\n return ForwardedResultView;\n}\n","// @datarecce/ui/primitives - Building block components for custom composition\n// These are pure presentation components - no data fetching, just props and callbacks.\n\n\"use client\";\n\n/**\n * Version marker for the primitives surface.\n */\nexport const PRIMITIVES_API_VERSION = \"0.1.0\";\n\n// =============================================================================\n// LINEAGE PRIMITIVES\n// =============================================================================\n\n/**\n * Lineage column node primitives for column-level lineage rendering.\n *\n * @remarks\n * Exports: COLUMN_NODE_HEIGHT, COLUMN_NODE_WIDTH, ColumnTransformationType,\n * LineageColumnNode, LineageColumnNodeData, LineageColumnNodeProps.\n */\nexport {\n COLUMN_NODE_HEIGHT,\n COLUMN_NODE_WIDTH,\n type ColumnTransformationType,\n LineageColumnNode,\n type LineageColumnNodeData,\n type LineageColumnNodeProps,\n} from \"./components/lineage/columns\";\n/**\n * Lineage edge primitives for graph edges.\n *\n * @remarks\n * Exports: EdgeChangeStatus, LineageEdge, LineageEdgeData, LineageEdgeProps.\n */\nexport {\n type EdgeChangeStatus,\n LineageEdge,\n type LineageEdgeData,\n type LineageEdgeProps,\n} from \"./components/lineage/edges\";\n/**\n * Lineage legend primitives for change status and transformation types.\n *\n * @remarks\n * Exports: ChangeStatusLegendItem, LineageLegend, LineageLegendProps,\n * TransformationLegendItem.\n */\nexport {\n type ChangeStatusLegendItem,\n LineageLegend,\n type LineageLegendProps,\n type TransformationLegendItem,\n} from \"./components/lineage/legend\";\n/**\n * Lineage node primitives for graph nodes.\n *\n * @remarks\n * Exports: LineageNode, LineageNodeData, LineageNodeProps, NodeChangeStatus.\n */\nexport {\n LineageNode,\n type LineageNodeData,\n type LineageNodeProps,\n type NodeChangeStatus,\n} from \"./components/lineage/nodes\";\n\n// =============================================================================\n// CHECK PRIMITIVES\n// =============================================================================\n\n/**\n * Check action primitives for check-level actions.\n *\n * @remarks\n * Exports: CheckAction, CheckActions, CheckActionsProps, CheckActionType.\n */\nexport {\n type CheckAction,\n CheckActions,\n type CheckActionsProps,\n type CheckActionType,\n} from \"./components/check/CheckActions\";\n/**\n * Check breadcrumb primitive for inline name editing.\n *\n * @remarks\n * Exports: CheckBreadcrumb, CheckBreadcrumbProps.\n */\nexport {\n CheckBreadcrumb,\n type CheckBreadcrumbProps,\n} from \"./components/check/CheckBreadcrumb\";\n/**\n * Check card primitives for individual check display.\n *\n * @remarks\n * Exports: CheckCard, CheckCardData, CheckCardProps, CheckRunStatus, CheckType.\n */\nexport {\n CheckCard,\n type CheckCardData,\n type CheckCardProps,\n type CheckRunStatus,\n type CheckType,\n} from \"./components/check/CheckCard\";\n/**\n * Check description primitives for inline description editing.\n *\n * @remarks\n * Exports: CheckDescription, CheckDescriptionProps.\n */\nexport {\n CheckDescription,\n type CheckDescriptionProps,\n} from \"./components/check/CheckDescription\";\n/**\n * Check detail primitives for full check view content.\n *\n * @remarks\n * Exports: CheckDetail, CheckDetailProps, CheckDetailTab.\n */\nexport {\n CheckDetail,\n type CheckDetailProps,\n type CheckDetailTab,\n} from \"./components/check/CheckDetail\";\n/**\n * Check empty state primitive.\n *\n * @remarks\n * Exports: CheckEmptyState, CheckEmptyStateProps.\n */\nexport {\n CheckEmptyState,\n type CheckEmptyStateProps,\n} from \"./components/check/CheckEmptyState\";\n/**\n * Check list primitives for rendering the checks list.\n *\n * @remarks\n * Exports: CheckList, CheckListProps.\n */\nexport { CheckList, type CheckListProps } from \"./components/check/CheckList\";\n\n/**\n * Lineage diff view primitives for check results.\n *\n * @remarks\n * Exports: LineageDiffView, LineageDiffViewOptions, LineageDiffViewProps, LineageViewRef.\n */\nexport {\n LineageDiffView,\n type LineageDiffViewOptions,\n type LineageDiffViewProps,\n type LineageViewRef,\n} from \"./components/check/LineageDiffView\";\n\n/**\n * Preset check template primitives.\n *\n * @remarks\n * Exports: GenerateCheckTemplateOptions, generateCheckTemplate,\n * PresetCheckTemplateView, PresetCheckTemplateViewProps.\n */\nexport {\n type GenerateCheckTemplateOptions,\n generateCheckTemplate,\n PresetCheckTemplateView,\n type PresetCheckTemplateViewProps,\n} from \"./components/check/PresetCheckTemplateView\";\n\n/**\n * Check timeline primitives.\n *\n * @remarks\n * Exports: CommentInput, CommentInputProps, TimelineActor, TimelineEvent,\n * TimelineEventData, TimelineEventProps, TimelineEventType.\n */\nexport {\n CommentInput,\n type CommentInputProps,\n type TimelineActor,\n TimelineEvent,\n type TimelineEventData,\n type TimelineEventProps,\n type TimelineEventType,\n} from \"./components/check/timeline\";\n\n/**\n * Check utility functions.\n *\n * @remarks\n * Exports: buildCheckDescription, buildCheckTitle, formatSqlAsMarkdown,\n * isDisabledByNoResult.\n */\nexport {\n buildCheckDescription,\n buildCheckTitle,\n formatSqlAsMarkdown,\n isDisabledByNoResult,\n} from \"./components/check/utils\";\n\n// =============================================================================\n// RUN PRIMITIVES\n// =============================================================================\n\n/**\n * Run list primitives for run history display.\n *\n * @remarks\n * Exports: RunList, RunListItem, RunListItemData, RunListItemProps, RunListProps.\n */\nexport {\n RunList,\n RunListItem,\n type RunListItemData,\n type RunListItemProps,\n type RunListProps,\n} from \"./components/run/RunList\";\n\n/**\n * Run progress primitives for execution indicators.\n *\n * @remarks\n * Exports: RunProgress, RunProgressOverlay, RunProgressOverlayProps,\n * RunProgressProps, RunProgressVariant.\n */\nexport {\n RunProgress,\n RunProgressOverlay,\n type RunProgressOverlayProps,\n type RunProgressProps,\n type RunProgressVariant,\n} from \"./components/run/RunProgress\";\n\n/**\n * Run status badge primitives.\n *\n * @remarks\n * Exports: formatRunDate, formatRunDateTime, inferRunStatus, RunStatus,\n * RunStatusAndDate, RunStatusAndDateProps, RunStatusBadge, RunStatusBadgeProps,\n * RunStatusWithDate, RunStatusWithDateProps.\n */\nexport {\n formatRunDate,\n formatRunDateTime,\n inferRunStatus,\n type RunStatus,\n RunStatusAndDate,\n type RunStatusAndDateProps,\n RunStatusBadge,\n type RunStatusBadgeProps,\n RunStatusWithDate,\n type RunStatusWithDateProps,\n} from \"./components/run/RunStatusBadge\";\n\n/**\n * Run toolbar primitives for warnings and actions.\n *\n * @remarks\n * Exports: DiffViewOptions, RunToolbar, RunToolbarProps.\n */\nexport {\n type DiffViewOptions,\n RunToolbar,\n type RunToolbarProps,\n} from \"./components/run/RunToolbar\";\n\n/**\n * Run registry types for extensibility.\n *\n * @remarks\n * Exports: RefTypes, RegistryEntry, RunFormParamTypes, RunFormProps,\n * RunResultViewProps, ViewOptionTypes.\n */\nexport type {\n RefTypes,\n RegistryEntry,\n RunFormParamTypes,\n RunFormProps,\n RunResultViewProps,\n ViewOptionTypes,\n} from \"./components/run/types\";\n\n// =============================================================================\n// DATA PRIMITIVES\n// =============================================================================\n\n/**\n * Histogram chart primitives (Chart.js based).\n *\n * @remarks\n * Exports: ChartBarColors, ChartThemeColors, getChartBarColors,\n * getChartThemeColors, HistogramChart, HistogramChartProps, HistogramDataset,\n * HistogramDataType.\n */\nexport {\n type ChartBarColors,\n type ChartThemeColors,\n getChartBarColors,\n getChartThemeColors,\n HistogramChart,\n type HistogramChartProps,\n type HistogramDataset,\n type HistogramDataType,\n} from \"./components/data/HistogramChart\";\n/**\n * Screenshot data grid primitives (AG Grid wrapper).\n *\n * @remarks\n * Exports: ColDef, ColGroupDef, DataGridHandle, DataGridRow, EmptyRowsRenderer,\n * EmptyRowsRendererProps, GetRowIdParams, GridReadyEvent, RecceDataGridHandle,\n * ScreenshotDataGrid, ScreenshotDataGridProps.\n */\nexport {\n type ColDef,\n type ColGroupDef,\n type DataGridHandle,\n type DataGridRow,\n EmptyRowsRenderer,\n type EmptyRowsRendererProps,\n type GetRowIdParams,\n type GridReadyEvent,\n type RecceDataGridHandle,\n ScreenshotDataGrid,\n type ScreenshotDataGridProps,\n} from \"./components/data/ScreenshotDataGrid\";\n\n/**\n * Top-K bar chart primitives (value distribution).\n *\n * @remarks\n * Exports: TopKBarChart, TopKBarChartProps, TopKDataset, TopKItem.\n */\nexport {\n TopKBarChart,\n type TopKBarChartProps,\n type TopKDataset,\n type TopKItem,\n} from \"./components/data/TopKBarChart\";\n\n// =============================================================================\n// SCHEMA PRIMITIVES\n// =============================================================================\n\n/**\n * Schema diff types for base vs current comparison.\n *\n * @remarks\n * Exports: SchemaDiffRow, SchemaDiffStatus.\n */\nexport type {\n SchemaDiffRow,\n SchemaDiffStatus,\n} from \"./components/schema/types\";\n\n// =============================================================================\n// EDITOR PRIMITIVES\n// =============================================================================\n\n/**\n * Code editor primitives (CodeMirror based).\n *\n * @remarks\n * Exports: CodeEditor, CodeEditorLanguage, CodeEditorProps, CodeEditorTheme.\n */\nexport {\n CodeEditor,\n type CodeEditorLanguage,\n type CodeEditorProps,\n type CodeEditorTheme,\n} from \"./components/editor/CodeEditor\";\n\n/**\n * Diff editor primitives (CodeMirror merge view).\n *\n * @remarks\n * Exports: DiffEditor, DiffEditorLanguage, DiffEditorProps, DiffEditorTheme.\n */\nexport {\n DiffEditor,\n type DiffEditorLanguage,\n type DiffEditorProps,\n type DiffEditorTheme,\n} from \"./components/editor/DiffEditor\";\n\n// =============================================================================\n// UI PRIMITIVES\n// =============================================================================\n\n/**\n * Changed-only checkbox primitive for filtering diff results.\n *\n * @remarks\n * Exports: ChangedOnlyCheckbox, ChangedOnlyCheckboxProps.\n */\nexport {\n ChangedOnlyCheckbox,\n type ChangedOnlyCheckboxProps,\n} from \"./components/ui/ChangedOnlyCheckbox\";\n/**\n * Diff display mode switch primitive for toggling inline/side-by-side views.\n *\n * @remarks\n * Exports: DiffDisplayMode, DiffDisplayModeSwitch, DiffDisplayModeSwitchProps.\n */\nexport {\n type DiffDisplayMode,\n DiffDisplayModeSwitch,\n type DiffDisplayModeSwitchProps,\n} from \"./components/ui/DiffDisplayModeSwitch\";\n/**\n * Diff text primitives for inline diff visualization.\n *\n * @remarks\n * Exports: DiffText, DiffTextProps.\n */\nexport { DiffText, type DiffTextProps } from \"./components/ui/DiffText\";\n/**\n * Diff text with toast primitive - DiffText with copy notification feedback.\n *\n * @remarks\n * Exports: DiffTextWithToast, DiffTextWithToastProps.\n */\nexport {\n DiffTextWithToast,\n type DiffTextWithToastProps,\n} from \"./components/ui/DiffTextWithToast\";\n/**\n * Dropdown values input primitives (multi-select with filtering).\n *\n * @remarks\n * Exports: DropdownValuesInput, DropdownValuesInputProps, DropdownValuesInputSize.\n */\nexport {\n DropdownValuesInput,\n type DropdownValuesInputProps,\n type DropdownValuesInputSize,\n} from \"./components/ui/DropdownValuesInput\";\n/**\n * Empty state primitives.\n *\n * @remarks\n * Exports: EmptyState, EmptyStateProps.\n */\nexport { EmptyState, type EmptyStateProps } from \"./components/ui/EmptyState\";\n/**\n * External link confirmation dialog primitives.\n *\n * @remarks\n * Exports: ExternalLinkConfirmDialog, ExternalLinkConfirmDialogProps, truncateUrl.\n */\nexport {\n ExternalLinkConfirmDialog,\n type ExternalLinkConfirmDialogProps,\n truncateUrl,\n} from \"./components/ui/ExternalLinkConfirmDialog\";\n/**\n * Markdown content primitives.\n *\n * @remarks\n * Exports: MarkdownContent, MarkdownContentProps.\n */\nexport {\n MarkdownContent,\n type MarkdownContentProps,\n} from \"./components/ui/MarkdownContent\";\n/**\n * Screenshot box primitives.\n *\n * @remarks\n * Exports: ScreenshotBox, ScreenshotBoxProps.\n */\nexport {\n ScreenshotBox,\n type ScreenshotBoxProps,\n} from \"./components/ui/ScreenshotBox\";\n/**\n * Split pane primitives (resizable layout).\n *\n * @remarks\n * Exports: HSplit, SplitProps, VSplit.\n */\nexport { HSplit, type SplitProps, VSplit } from \"./components/ui/Split\";\n/**\n * Split pane primitives with explicit direction.\n *\n * @remarks\n * Exports: SplitDirection, SplitPane, SplitPaneProps.\n */\nexport {\n type SplitDirection,\n SplitPane,\n type SplitPaneProps,\n} from \"./components/ui/SplitPane\";\n/**\n * Toast notification system primitives.\n *\n * @remarks\n * Exports: Toaster, ToasterProvider, ToastOptions, toaster, useToaster.\n */\nexport {\n Toaster,\n ToasterProvider,\n type ToastOptions,\n toaster,\n useToaster,\n} from \"./components/ui/Toaster\";\n/**\n * Toggle switch primitive for boolean value inputs.\n *\n * @remarks\n * Exports: ToggleSwitch, ToggleSwitchProps.\n */\nexport {\n ToggleSwitch,\n type ToggleSwitchProps,\n} from \"./components/ui/ToggleSwitch\";\n\n// =============================================================================\n// RESULT VIEW PRIMITIVES\n// =============================================================================\n// NOTE: Result view factory canonical in @datarecce/ui/result\n\n/**\n * Result view factory primitives.\n * @deprecated Import from @datarecce/ui/result instead\n */\nexport {\n type CreatedResultViewProps,\n createResultView,\n type ResultViewConfig,\n type ResultViewData,\n type ResultViewProps,\n type ResultViewRef,\n type ResultViewTransformOptions,\n type ScreenshotWrapperType,\n} from \"./components/result\";\n","\"use client\";\n\n/**\n * @file HistogramResultView.tsx\n * @description Framework-agnostic Histogram result view components for @datarecce/ui\n *\n * These components use the createResultView factory pattern and can be used by both\n * Recce OSS and Recce Cloud. They accept generic Run types and use type guards\n * for validation.\n *\n * The components display histogram diff data as a chart:\n * - HistogramDiffResultView: Diff between base and current histogram distributions\n */\n\nimport Box from \"@mui/material/Box\";\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n type HistogramDiffParams,\n type HistogramDiffResult,\n isHistogramDiffRun,\n type Run,\n} from \"../../api\";\nimport { HistogramChart, type HistogramDataType } from \"../../primitives\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with histogram_diff result\n */\nexport type HistogramDiffRun = Run & {\n type: \"histogram_diff\";\n params?: HistogramDiffParams;\n result?: HistogramDiffResult;\n};\n\n/**\n * Props for HistogramDiffResultView component\n */\nexport interface HistogramResultViewProps\n extends CreatedResultViewProps<unknown> {\n run: HistogramDiffRun | unknown;\n}\n\n// ============================================================================\n// Type Guard (wrapper to accept unknown)\n// ============================================================================\n\nfunction isHistogramDiffRunGuard(run: unknown): run is HistogramDiffRun {\n return isHistogramDiffRun(run as Run);\n}\n\n// ============================================================================\n// Factory-Created Component\n// ============================================================================\n\n/**\n * Result view for histogram diff comparison between base and current environments\n *\n * Displays a chart comparing histogram distributions between base and current.\n * The chart title shows the model and column name.\n *\n * @example\n * ```tsx\n * <HistogramDiffResultView run={histogramDiffRun} ref={boxRef} />\n * ```\n */\nexport const HistogramDiffResultView = createResultView<\n HistogramDiffRun,\n unknown,\n HTMLDivElement\n>({\n displayName: \"HistogramDiffResultView\",\n typeGuard: isHistogramDiffRunGuard,\n expectedRunType: \"histogram_diff\",\n screenshotWrapper: \"box\",\n conditionalEmptyState: (run) => {\n const base = run.result?.base;\n const current = run.result?.current;\n if (!base || !current) {\n return <div>Loading...</div>;\n }\n return null;\n },\n transformData: (run): ResultViewData | null => {\n const params = run.params as HistogramDiffParams;\n const base = run.result?.base;\n const current = run.result?.current;\n const min = run.result?.min;\n const max = run.result?.max;\n const binEdges = run.result?.bin_edges ?? [];\n\n // This shouldn't happen due to conditionalEmptyState, but type safety\n if (!base || !current) {\n return { isEmpty: true };\n }\n\n // Map column_type to HistogramDataType\n const columnType = (run.params?.column_type ?? \"numeric\") as string;\n const dataType: HistogramDataType =\n columnType === \"datetime\"\n ? \"datetime\"\n : columnType === \"string\"\n ? \"string\"\n : \"numeric\";\n\n return {\n content: (\n <Box sx={{ display: \"flex\", flexDirection: \"row\" }}>\n <Box sx={{ flex: 1 }} />\n <Box sx={{ width: \"80%\", height: \"35vh\", m: 4 }}>\n <HistogramChart\n title={`Model ${params.model}.${params.column_name}`}\n dataType={dataType}\n baseData={{ counts: base.counts }}\n currentData={{ counts: current.counts }}\n min={min}\n max={max}\n samples={base.total}\n binEdges={binEdges}\n />\n </Box>\n <Box sx={{ flex: 1 }} />\n </Box>\n ),\n };\n },\n}) as ForwardRefExoticComponent<\n HistogramResultViewProps & RefAttributes<HTMLDivElement>\n>;\n","\"use client\";\n\n/**\n * @file ColumnNameCell.tsx\n * @description A cell renderer component for displaying column names in schema tables.\n *\n * This component renders a column name with an optional context menu for performing\n * diff operations (Profile Diff, Histogram Diff, Top-k Diff, Value Diff) on the column.\n * It also displays a loading spinner when column-level lineage is being computed.\n *\n * @example\n * ```tsx\n * <ColumnNameCell\n * model={nodeData}\n * row={schemaDiffRow}\n * singleEnv={false}\n * cllRunning={false}\n * showMenu={true}\n * />\n * ```\n */\n\nimport Box from \"@mui/material/Box\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport IconButton from \"@mui/material/IconButton\";\nimport ListSubheader from \"@mui/material/ListSubheader\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport { type MouseEvent, useState } from \"react\";\nimport { VscKebabVertical } from \"react-icons/vsc\";\nimport type { NodeData } from \"../../api\";\nimport {\n useLineageGraphContext,\n useLineageViewContext,\n useRecceActionContext,\n useRecceInstanceContext,\n} from \"../../contexts\";\nimport { supportsHistogramDiff } from \"../histogram\";\nimport { buildColumnTooltip, DataTypeIcon } from \"../ui/DataTypeIcon\";\nimport type { SchemaDiffRow } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Props for the ColumnNameCell component\n */\nexport interface ColumnNameCellProps {\n /** The model/node data containing column information */\n model: NodeData;\n /** The schema diff row data for this column */\n row: SchemaDiffRow;\n /** Whether viewing a single environment (disables diff menu) */\n singleEnv?: boolean;\n /** Whether column-level lineage is currently being computed */\n cllRunning?: boolean;\n /** Whether to show the context menu (defaults to true) */\n showMenu?: boolean;\n /** Callback when user clicks a definition-changed badge to view SQL diff */\n onViewCode?: () => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * ColumnNameCell - Renders a column name with optional diff action menu\n *\n * Displays the column name with:\n * - A context menu for initiating diff operations (when applicable)\n * - A loading spinner when column-level lineage is running\n * - Tooltip indicating column lineage viewing capability\n *\n * The menu is hidden when:\n * - showMenu is false\n * - singleEnv is true (no comparison available)\n * - The model is a source (sources don't support diff operations)\n */\nexport function ColumnNameCell({\n model,\n row,\n singleEnv,\n cllRunning,\n showMenu = true,\n onViewCode,\n}: ColumnNameCellProps) {\n const lineageViewContext = useLineageViewContext();\n const { isActionAvailable } = useLineageGraphContext();\n const { runAction } = useRecceActionContext();\n const { featureToggles } = useRecceInstanceContext();\n const {\n name,\n baseType,\n currentType,\n baseIndex,\n currentIndex,\n reordered,\n definitionChanged,\n } = row;\n const columnType =\n currentType ??\n baseType ??\n ((row as Record<string, unknown>).type as string | undefined);\n const isAdded = baseIndex === undefined && currentIndex !== undefined;\n const isRemoved = baseIndex !== undefined && currentIndex === undefined;\n const isTypeChanged = !isAdded && !isRemoved && baseType !== currentType;\n const hasStructuralChange =\n !isAdded && !isRemoved && (baseType !== currentType || reordered === true);\n\n const columnStatus = singleEnv\n ? \"unchanged\"\n : isAdded\n ? \"added\"\n : isRemoved\n ? \"removed\"\n : isTypeChanged\n ? \"type_changed\"\n : definitionChanged\n ? \"definition_changed\"\n : \"unchanged\";\n\n const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);\n const menuOpen = Boolean(anchorEl);\n\n const handleMenuClick = (event: MouseEvent<HTMLElement>) => {\n event.stopPropagation();\n setAnchorEl(event.currentTarget);\n };\n\n const handleMenuClose = () => {\n setAnchorEl(null);\n };\n\n const handleProfileDiff = () => {\n runAction(\n \"profile_diff\",\n { model: model.name, columns: [name] },\n {\n showForm: false,\n trackProps: {\n action: \"profile_diff\",\n source: \"schema_column_menu\",\n node_count: 1,\n },\n },\n );\n };\n\n const handleHistogramDiff = () => {\n runAction(\n \"histogram_diff\",\n { model: model.name, column_name: name, column_type: columnType },\n {\n showForm: false,\n trackProps: {\n action: \"histogram_diff\",\n source: \"schema_column_menu\",\n node_count: 1,\n },\n },\n );\n };\n\n const handleTopkDiff = () => {\n runAction(\n \"top_k_diff\",\n { model: model.name, column_name: name, k: 50 },\n {\n showForm: false,\n trackProps: {\n action: \"top_k_diff\",\n source: \"schema_column_menu\",\n node_count: 1,\n },\n },\n );\n };\n\n const handleValueDiff = () => {\n runAction(\n \"value_diff\",\n { model: model.name, columns: [name] },\n {\n showForm: true,\n trackProps: {\n action: \"value_diff\",\n source: \"schema_column_menu\",\n node_count: 1,\n },\n },\n );\n };\n\n const addedOrRemoved = !baseType || !currentType;\n const isCllDisabled =\n lineageViewContext === undefined ||\n !isActionAvailable(\"change_analysis\") ||\n (baseIndex !== undefined && currentIndex === undefined);\n\n const tooltipTitle = buildColumnTooltip({\n name,\n status: columnStatus,\n baseType,\n currentType,\n cllAvailable: !isCllDisabled,\n });\n\n return (\n <Tooltip title={tooltipTitle} placement=\"top\">\n <Box sx={{ display: \"flex\", alignItems: \"center\", gap: \"3px\" }}>\n {hasStructuralChange && (\n <span className=\"schema-change-badge schema-change-badge-changed\">\n ~\n </span>\n )}\n {isAdded && (\n <span className=\"schema-change-badge schema-change-badge-added\">\n +\n </span>\n )}\n {isRemoved && (\n <span className=\"schema-change-badge schema-change-badge-removed\">\n -\n </span>\n )}\n {definitionChanged && (\n <Tooltip\n title=\"Definition changed — click to view code\"\n placement=\"top\"\n onMouseOver={(e) => e.stopPropagation()}\n >\n {onViewCode ? (\n <button\n type=\"button\"\n className=\"schema-change-badge schema-change-badge-changed schema-change-badge-clickable\"\n onClick={(e) => {\n e.stopPropagation();\n onViewCode();\n }}\n >\n ~\n </button>\n ) : (\n <span className=\"schema-change-badge schema-change-badge-changed\">\n ~\n </span>\n )}\n </Tooltip>\n )}\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {name}\n </Box>\n {isTypeChanged ? (\n <Box\n component=\"span\"\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"2px\",\n ml: \"4px\",\n }}\n >\n {baseType && (\n <Box\n component=\"span\"\n sx={{ textDecoration: \"line-through\", opacity: 0.6 }}\n >\n <DataTypeIcon type={baseType} size={16} disableTooltip />\n </Box>\n )}\n <Box component=\"span\" sx={{ fontSize: \"0.7em\", opacity: 0.5 }}>\n →\n </Box>\n {currentType && (\n <DataTypeIcon type={currentType} size={16} disableTooltip />\n )}\n </Box>\n ) : (\n columnType && (\n <Box component=\"span\" sx={{ ml: \"4px\" }}>\n <DataTypeIcon type={columnType} size={16} disableTooltip />\n </Box>\n )\n )}\n <Box sx={{ flex: 1 }} />\n {cllRunning && <CircularProgress size={12} color=\"inherit\" />}\n {showMenu && !singleEnv && model.resource_type !== \"source\" && (\n <>\n <IconButton\n aria-label=\"Column options\"\n className=\"row-context-menu\"\n size=\"small\"\n disabled={featureToggles.disableDatabaseQuery}\n onClick={handleMenuClick}\n >\n <VscKebabVertical />\n </IconButton>\n <Menu\n anchorEl={anchorEl}\n open={menuOpen}\n onClose={handleMenuClose}\n slotProps={{\n list: { sx: { lineHeight: \"20px\" } },\n }}\n >\n <ListSubheader sx={{ m: 0, p: \"4px 12px\", lineHeight: \"20px\" }}>\n Diff\n </ListSubheader>\n <MenuItem\n onClick={() => {\n handleProfileDiff();\n handleMenuClose();\n }}\n disabled={addedOrRemoved}\n sx={{ fontSize: \"0.85rem\" }}\n >\n Profile Diff\n </MenuItem>\n <MenuItem\n onClick={() => {\n handleHistogramDiff();\n handleMenuClose();\n }}\n disabled={\n addedOrRemoved ||\n (columnType ? !supportsHistogramDiff(columnType) : true)\n }\n sx={{ fontSize: \"0.85rem\" }}\n >\n Histogram Diff\n </MenuItem>\n <MenuItem\n onClick={() => {\n handleTopkDiff();\n handleMenuClose();\n }}\n disabled={addedOrRemoved}\n sx={{ fontSize: \"0.85rem\" }}\n >\n Top-k Diff\n </MenuItem>\n <MenuItem\n onClick={() => {\n handleValueDiff();\n handleMenuClose();\n }}\n disabled={addedOrRemoved}\n sx={{ fontSize: \"0.85rem\" }}\n >\n Value Diff\n </MenuItem>\n </Menu>\n </>\n )}\n </Box>\n </Tooltip>\n );\n}\n","\"use client\";\n\n/**\n * @file schemaCells.tsx\n * @description Cell components and render functions for Schema grid views\n */\n\nimport type { ICellRendererParams } from \"ag-grid-community\";\nimport React from \"react\";\nimport type { NodeData, RowObjectType } from \"../../../api\";\nimport type { SchemaDiffRow, SchemaRow } from \"../../schema\";\nimport { ColumnNameCell } from \"../../schema/ColumnNameCell\";\n\n// ============================================================================\n// Render Functions for toSchemaDataGrid.ts\n// ============================================================================\n\n/**\n * Creates a cellRenderer function for schema diff column names\n */\nexport function createSchemaColumnNameRenderer(\n node: NodeData,\n cllRunningMap?: Map<string, boolean>,\n showMenu?: boolean,\n onViewCode?: () => void,\n): (params: ICellRendererParams<SchemaDiffRow>) => React.ReactNode {\n return (params) => {\n const row = params.data;\n if (!row) return null;\n return (\n <ColumnNameCell\n model={node}\n row={row}\n cllRunning={cllRunningMap?.get(row.name) ?? false}\n showMenu={showMenu}\n onViewCode={onViewCode}\n />\n );\n };\n}\n\n/**\n * Creates a cellRenderer function for single-env schema column names\n */\nexport function createSingleEnvColumnNameRenderer(\n node: NodeData,\n cllRunningMap?: Map<string, boolean>,\n showMenu?: boolean,\n): (params: ICellRendererParams<SchemaRow>) => React.ReactNode {\n return (params) => {\n const row = params.data;\n if (!row) return null;\n return (\n <ColumnNameCell\n model={node}\n row={row}\n cllRunning={cllRunningMap?.get(row.name) ?? false}\n singleEnv\n showMenu={showMenu}\n />\n );\n };\n}\n\n// ============================================================================\n// Merged Column Render Functions for Compressed Schema View\n// ============================================================================\n\n/**\n * Renders the merged index column.\n * Shows currentIndex for normal/added rows, baseIndex for removed rows.\n * For reordered rows, shows strikethrough old → bold new.\n */\nexport function renderIndexCell(\n params: ICellRendererParams<RowObjectType>,\n): React.ReactNode {\n if (!params.data) {\n return null;\n }\n const row = params.data;\n\n const { baseIndex, currentIndex, reordered } = row;\n const isRemoved = currentIndex === undefined;\n\n if (\n reordered &&\n baseIndex !== undefined &&\n currentIndex !== undefined &&\n baseIndex !== currentIndex\n ) {\n return (\n <span>\n <span className=\"schema-index-old\">{baseIndex}</span>\n <span className=\"schema-index-new\">{currentIndex}</span>\n </span>\n );\n }\n\n const value = isRemoved ? (baseIndex ?? \"-\") : (currentIndex ?? \"-\");\n return <span>{value}</span>;\n}\n","/**\n * @file toSchemaDataGrid.ts\n * @description Grid generator for schema diff and single-environment schema views\n *\n * This file is intentionally .ts (not .tsx) - all JSX rendering is delegated\n * to schemaCells.tsx via render functions.\n */\n\nimport \"../../../components/schema/style.css\";\nimport type { ColDef, ColGroupDef } from \"ag-grid-community\";\nimport {\n type NodeColumnData,\n type NodeData,\n type RowObjectType,\n} from \"../../../api\";\nimport {\n createSchemaColumnNameRenderer,\n createSingleEnvColumnNameRenderer,\n renderIndexCell,\n} from \"../../../components/ui/dataGrid/schemaCells\";\nimport { mergeKeysWithStatus } from \"../../../utils\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface SchemaDiffRow extends RowObjectType {\n name: string;\n reordered?: boolean;\n currentIndex?: number;\n baseIndex?: number;\n currentType?: string;\n baseType?: string;\n /** True when the column's SQL definition changed but name/type stayed the same */\n definitionChanged?: boolean;\n}\n\nexport interface SchemaRow extends RowObjectType {\n name: string;\n index: number;\n type?: string;\n}\n\ntype SchemaDiff = Record<string, SchemaDiffRow>;\n\nexport interface SchemaDataGridOptions {\n /** Node data for context menu actions */\n node?: NodeData;\n /** Map of column names to CLL loading state */\n cllRunningMap?: Map<string, boolean>;\n /** Whether to show the column action menu (default: true) */\n showMenu?: boolean;\n /** Per-column change status from breaking change analysis */\n columnChanges?: Record<string, \"added\" | \"removed\" | \"modified\"> | null;\n /** Callback when user clicks a definition-changed badge to view SQL diff */\n onViewCode?: () => void;\n}\n\nexport interface SchemaDataGridResult {\n columns: (ColDef<SchemaDiffRow> | ColGroupDef<SchemaDiffRow>)[];\n rows: SchemaDiffRow[];\n}\n\nexport interface SingleEnvSchemaDataGridResult {\n columns: (ColDef<SchemaRow> | ColGroupDef<SchemaRow>)[];\n rows: SchemaRow[];\n}\n\n// ============================================================================\n// Data Transformation\n// ============================================================================\n\n/**\n * Merges base and current column schemas into a diff structure\n */\nexport function mergeColumns(\n baseColumns: NodeData[\"columns\"] = {},\n currentColumns: NodeData[\"columns\"] = {},\n): SchemaDiff {\n const result: SchemaDiff = {};\n const mergedStatus = mergeKeysWithStatus(\n Object.keys(baseColumns),\n Object.keys(currentColumns),\n );\n\n Object.entries(mergedStatus).forEach(([name, status]) => {\n result[name] = {\n name,\n reordered: status === \"reordered\",\n __status: undefined,\n };\n });\n\n let filteredIndex = 0;\n Object.entries(baseColumns).forEach(([name, column]) => {\n if (column != null) {\n result[name].baseIndex = filteredIndex += 1;\n result[name].baseType = column.type;\n }\n });\n\n filteredIndex = 0;\n Object.entries(currentColumns).forEach(([name, column]) => {\n if (column != null) {\n result[name].currentIndex = filteredIndex += 1;\n result[name].currentType = column.type;\n }\n });\n\n return result;\n}\n\n// ============================================================================\n// Main Generator Functions\n// ============================================================================\n\n/**\n * Generates grid configuration for schema diff view\n * Uses merged columns: Index (merged base/current), Name (with inline DataTypeIcon)\n */\nexport function toSchemaDataGrid(\n schemaDiff: SchemaDiff,\n options: SchemaDataGridOptions = {},\n): SchemaDataGridResult {\n const { node, cllRunningMap, showMenu, columnChanges, onViewCode } = options;\n\n const columns: ColDef<SchemaDiffRow>[] = [\n {\n field: \"index\",\n headerName: \"\",\n resizable: true,\n minWidth: 35,\n width: 35,\n cellRenderer: renderIndexCell,\n cellClass: \"schema-column schema-column-index\",\n },\n {\n field: \"name\",\n headerName: \"Name\",\n resizable: true,\n cellRenderer: node\n ? createSchemaColumnNameRenderer(\n node,\n cllRunningMap,\n showMenu,\n onViewCode,\n )\n : undefined,\n cellClass: \"schema-column\",\n // Include definitionChanged in the value so ag-grid re-renders the cell\n // when the badge state changes (e.g., after Impact Radius completes)\n valueGetter: (params) => {\n const row = params.data;\n return row ? `${row.name}|${row.definitionChanged ?? false}` : \"\";\n },\n },\n ];\n\n const rows = Object.values(schemaDiff);\n\n // Mark columns whose SQL definition changed but have no other visible change\n if (columnChanges) {\n for (const row of rows) {\n const isAdded = row.baseIndex === undefined;\n const isRemoved = row.currentIndex === undefined;\n const isTypeChanged =\n !isAdded && !isRemoved && row.baseType !== row.currentType;\n const changeStatus = columnChanges[row.name];\n\n if (\n changeStatus === \"modified\" &&\n !isAdded &&\n !isRemoved &&\n !isTypeChanged &&\n !row.reordered\n ) {\n row.definitionChanged = true;\n }\n }\n }\n\n return { columns, rows };\n}\n\n/**\n * Generates grid configuration for single-environment schema view\n */\nexport function toSingleEnvDataGrid(\n nodeColumns: NodeData[\"columns\"] = {},\n options: SchemaDataGridOptions = {},\n): SingleEnvSchemaDataGridResult {\n const { node, cllRunningMap, showMenu } = options;\n\n const nodeColumnList = Object.entries(nodeColumns).filter(\n ([_, column]) => column != null,\n ) as [string, NodeColumnData][];\n\n const rows: SchemaRow[] = nodeColumnList.map(([name, column], index) => ({\n name,\n index: index + 1,\n type: column.type,\n __status: undefined,\n }));\n\n const columns: ColDef<SchemaRow>[] = [\n {\n field: \"index\",\n headerName: \"\",\n resizable: true,\n minWidth: 35,\n width: 35,\n cellClass: \"schema-column schema-column-index\",\n },\n {\n field: \"name\",\n headerName: \"Name\",\n resizable: true,\n cellRenderer: node\n ? createSingleEnvColumnNameRenderer(node, cllRunningMap, showMenu)\n : undefined,\n cellClass: \"schema-column\",\n },\n ];\n\n return { columns, rows };\n}\n","/**\n * @file valueDiffCells.tsx\n * @description Cell components and render functions for Value Diff summary grid\n *\n * Provides specialized cell renderers for the value diff summary view:\n * - PrimaryKeyIndicatorCell: Shows key icon for primary key columns\n * - ValueDiffColumnNameCell: Column name with context menu for drill-down\n * - MatchedPercentCell: Formatted percentage display\n *\n * Also exports render functions for use in toValueDataGrid.ts generator.\n */\n\nimport Box from \"@mui/material/Box\";\nimport IconButton from \"@mui/material/IconButton\";\nimport ListSubheader from \"@mui/material/ListSubheader\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport type { ICellRendererParams } from \"ag-grid-community\";\nimport React, { type MouseEvent, useState } from \"react\";\nimport { PiDotsThreeVertical } from \"react-icons/pi\";\nimport { VscKey } from \"react-icons/vsc\";\nimport { type RowObjectType, type ValueDiffParams } from \"../../../api\";\nimport {\n type RecceActionOptions,\n useRecceActionContext,\n useRecceInstanceContext,\n} from \"../../../contexts\";\n\n// ============================================================================\n// PrimaryKeyIndicatorCell\n// ============================================================================\n\nexport interface PrimaryKeyIndicatorCellProps {\n /** The column name to check */\n columnName: string;\n /** List of primary key column names */\n primaryKeys: string[];\n}\n\n/**\n * Cell component that displays a key icon for primary key columns\n *\n * @example\n * <PrimaryKeyIndicatorCell\n * columnName=\"user_id\"\n * primaryKeys={[\"user_id\", \"order_id\"]}\n * />\n */\nexport function PrimaryKeyIndicatorCell({\n columnName,\n primaryKeys,\n}: PrimaryKeyIndicatorCellProps) {\n const isPrimaryKey = primaryKeys.includes(columnName);\n\n return (\n <Box\n sx={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n height: \"100%\",\n }}\n >\n {isPrimaryKey && <VscKey />}\n </Box>\n );\n}\n\n// ============================================================================\n// ValueDiffColumnNameCell\n// ============================================================================\n\nexport interface ValueDiffColumnNameCellProps {\n /** The column name to display */\n column: string;\n /** Parameters from the value_diff run */\n params: ValueDiffParams;\n}\n\n/**\n * Cell component for column names with context menu for drill-down actions\n *\n * @description Renders the column name with a context menu that provides:\n * - \"Show mismatched values...\" - Opens form to configure detail view\n * - \"Show mismatched values for '{column}'\" - Directly shows mismatches for this column\n *\n * @example\n * <ValueDiffColumnNameCell\n * column=\"email\"\n * params={{ model: \"users\", primary_key: \"id\" }}\n * />\n */\nexport function ValueDiffColumnNameCell({\n params,\n column,\n}: ValueDiffColumnNameCellProps) {\n const { runAction } = useRecceActionContext();\n const { featureToggles } = useRecceInstanceContext();\n const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);\n const menuOpen = Boolean(anchorEl);\n\n const handleMenuClick = (event: MouseEvent<HTMLElement>) => {\n setAnchorEl(event.currentTarget);\n };\n\n const handleMenuClose = () => {\n setAnchorEl(null);\n };\n\n const handleValueDiffDetail = (\n paramsOverride?: Partial<ValueDiffParams>,\n options?: RecceActionOptions,\n ) => {\n const newParams = {\n ...params,\n ...paramsOverride,\n };\n\n runAction(\"value_diff_detail\", newParams, options);\n };\n\n return (\n <Box sx={{ display: \"flex\" }}>\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {column}\n </Box>\n <Box sx={{ flex: 1 }} />\n\n <IconButton\n aria-label=\"Column options\"\n className=\"row-context-menu\"\n size=\"small\"\n disabled={featureToggles.disableDatabaseQuery}\n onClick={handleMenuClick}\n >\n <PiDotsThreeVertical />\n </IconButton>\n <Menu\n anchorEl={anchorEl}\n open={menuOpen}\n onClose={handleMenuClose}\n slotProps={{\n list: { sx: { lineHeight: \"20px\" } },\n }}\n >\n <ListSubheader sx={{ fontSize: \"8pt\", lineHeight: \"20px\" }}>\n Action\n </ListSubheader>\n <MenuItem\n onClick={() => {\n handleValueDiffDetail({}, { showForm: true });\n handleMenuClose();\n }}\n sx={{ fontSize: \"10pt\" }}\n >\n Show mismatched values...\n </MenuItem>\n <MenuItem\n onClick={() => {\n handleValueDiffDetail({ columns: [column] }, { showForm: false });\n handleMenuClose();\n }}\n sx={{ fontSize: \"10pt\" }}\n >\n Show mismatched values for '{column}'\n </MenuItem>\n </Menu>\n </Box>\n );\n}\n\n// ============================================================================\n// MatchedPercentCell\n// ============================================================================\n\nexport interface MatchedPercentCellProps {\n /** The percentage value (0-1 scale) */\n value: number | undefined | null;\n}\n\n/**\n * Cell component for displaying match percentage with special formatting\n *\n * @description Formats the percentage value with special handling for edge cases:\n * - Values > 99.99% but < 100%: Shows \"~99.99 %\"\n * - Values > 0% but < 0.01%: Shows \"~0.01 %\"\n * - null/undefined: Shows \"N/A\"\n * - Other values: Shows \"XX.XX %\"\n *\n * @example\n * <MatchedPercentCell value={0.9542} /> // \"95.42 %\"\n * <MatchedPercentCell value={0.99999} /> // \"~99.99 %\"\n * <MatchedPercentCell value={null} /> // \"N/A\"\n */\nexport function MatchedPercentCell({ value }: MatchedPercentCellProps) {\n let displayValue = \"N/A\";\n\n if (value != null) {\n if (value > 0.9999 && value < 1) {\n displayValue = \"~99.99 %\";\n } else if (value < 0.0001 && value > 0) {\n displayValue = \"~0.01 %\";\n } else {\n displayValue = `${(value * 100).toFixed(2)} %`;\n }\n }\n\n return <Box sx={{ textAlign: \"right\" }}>{displayValue}</Box>;\n}\n\n// ============================================================================\n// Render Functions for toValueDataGrid.ts\n// ============================================================================\n\n/**\n * Creates a cellRenderer function for the primary key indicator column\n *\n * @param primaryKeys - List of primary key column names\n * @returns A cellRenderer function compatible with AG Grid\n */\nexport function createPrimaryKeyIndicatorRenderer(\n primaryKeys: string[],\n): (params: ICellRendererParams<RowObjectType>) => React.ReactNode {\n return (params) => {\n const row = params.data;\n if (!row) return null;\n return (\n <PrimaryKeyIndicatorCell\n columnName={String(row[\"0\"])}\n primaryKeys={primaryKeys}\n />\n );\n };\n}\n\n/**\n * Creates a cellRenderer function for the column name column\n *\n * @param params - ValueDiffParams from the run\n * @returns A cellRenderer function compatible with AG Grid\n */\nexport function createColumnNameRenderer(\n params: ValueDiffParams,\n): (cellParams: ICellRendererParams<RowObjectType>) => React.ReactNode {\n return (cellParams) => {\n const row = cellParams.data;\n const field = cellParams.colDef?.field ?? \"\";\n if (!row) return null;\n return (\n <ValueDiffColumnNameCell column={String(row[field])} params={params} />\n );\n };\n}\n\n/**\n * cellRenderer function for the matched percentage column\n *\n * @param params - ICellRendererParams from AG Grid\n * @returns React node displaying formatted percentage\n */\nexport function renderMatchedPercentCell(\n params: ICellRendererParams<RowObjectType>,\n): React.ReactNode {\n const row = params.data;\n const field = params.colDef?.field ?? \"\";\n if (!row) return null;\n return <MatchedPercentCell value={row[field] as number} />;\n}\n","/**\n * @file toValueDataGrid.ts\n * @description Grid generator for Value Diff summary view\n *\n * Generates columns and rows for displaying column-level match statistics\n * from a value_diff run. This shows a summary table where each row\n * represents a column's match count and percentage.\n *\n * NOTE: This is different from toValueDiffGrid which handles row-level diffs.\n * This generator is for the summary view showing column match statistics.\n *\n * This file is intentionally .ts (not .tsx) - all JSX rendering is delegated\n * to valueDiffCells.tsx via render functions.\n */\n\nimport type { CellClassParams, ColDef, ColGroupDef } from \"ag-grid-community\";\nimport {\n type DataFrame,\n type RowObjectType,\n type ValueDiffParams,\n type ValueDiffResult,\n} from \"../../../../api\";\n// Import directly from transforms to avoid circular dependency through barrel exports\nimport { dataFrameToRowObjects } from \"../../../../utils/transforms\";\nimport {\n createColumnNameRenderer,\n createPrimaryKeyIndicatorRenderer,\n renderMatchedPercentCell,\n} from \"../valueDiffCells\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for generating the value data grid\n */\nexport interface ValueDataGridOptions {\n /** Parameters from the value_diff run */\n params: ValueDiffParams;\n}\n\n/**\n * Result structure for the value data grid\n */\nexport interface ValueDataGridResult {\n columns: (ColDef<RowObjectType> | ColGroupDef<RowObjectType>)[];\n rows: RowObjectType[];\n}\n\n// ============================================================================\n// Column Schema Definition\n// ============================================================================\n\n/**\n * Schema definition for the value diff summary DataFrame\n *\n * This defines the expected structure of the data from the backend.\n * Using explicit schema avoids the \"basicColumns\" type fix pattern.\n */\nconst VALUE_DIFF_SUMMARY_SCHEMA: DataFrame[\"columns\"] = [\n { key: \"0\", name: \"Column\", type: \"text\" },\n { key: \"1\", name: \"Matched\", type: \"number\" },\n { key: \"2\", name: \"Matched %\", type: \"number\" },\n];\n\n// ============================================================================\n// Cell Class Functions\n// ============================================================================\n\n/**\n * Cell class function for matched columns\n * Applies \"diff-cell-modified\" class when match percentage is less than 100%\n */\nfunction getMatchedCellClass(\n params: CellClassParams<RowObjectType>,\n): string | undefined {\n const row = params.data;\n if (!row) return undefined;\n const value = row[\"2\"] as unknown as number | undefined;\n return value != null && value < 1 ? \"diff-cell-modified\" : undefined;\n}\n\n// ============================================================================\n// Comparators\n// ============================================================================\n\n/**\n * Custom comparator for the Matched % column that handles null/undefined values.\n * Null values are sorted last regardless of sort direction.\n */\nfunction matchedPercentComparator(\n a: number | null | undefined,\n b: number | null | undefined,\n): number {\n const aNull = a == null;\n const bNull = b == null;\n if (aNull && bNull) return 0;\n if (aNull) return 1;\n if (bNull) return -1;\n return a - b;\n}\n\n// ============================================================================\n// Column Definitions\n// ============================================================================\n\n/**\n * Column keys used in the value diff summary grid\n */\nconst COLUMN_KEYS = {\n PRIMARY_KEY_INDICATOR: \"__is_pk__\",\n COLUMN_NAME: \"0\",\n MATCHED_COUNT: \"1\",\n MATCHED_PERCENT: \"2\",\n} as const;\n\n/**\n * Creates column definitions for the value diff summary grid\n *\n * @param primaryKeys - Array of primary key column names\n * @param params - ValueDiffParams from the run\n * @returns Array of column definitions\n */\nfunction createColumnDefinitions(\n primaryKeys: string[],\n params: ValueDiffParams,\n): ColDef<RowObjectType>[] {\n return [\n // Primary key indicator column\n {\n field: COLUMN_KEYS.PRIMARY_KEY_INDICATOR,\n headerName: \"\",\n width: 30,\n maxWidth: 30,\n sortable: false,\n cellRenderer: createPrimaryKeyIndicatorRenderer(primaryKeys),\n },\n // Column name column with context menu\n {\n field: COLUMN_KEYS.COLUMN_NAME,\n headerName: \"Column\",\n resizable: true,\n cellRenderer: createColumnNameRenderer(params),\n cellClass: \"cell-show-context-menu\",\n },\n // Matched count column\n {\n field: COLUMN_KEYS.MATCHED_COUNT,\n headerName: \"Matched\",\n resizable: true,\n cellClass: getMatchedCellClass,\n },\n // Matched percentage column (default sort ascending — mismatches first)\n {\n field: COLUMN_KEYS.MATCHED_PERCENT,\n headerName: \"Matched %\",\n resizable: true,\n sort: \"asc\",\n comparator: matchedPercentComparator,\n cellRenderer: renderMatchedPercentCell,\n cellClass: getMatchedCellClass,\n },\n ];\n}\n\n// ============================================================================\n// Main Generator Function\n// ============================================================================\n\n/**\n * Generates grid configuration for value diff summary view\n *\n * @description Creates columns and rows for displaying column-level match\n * statistics. Each row represents a column from the compared data with\n * its match count and percentage.\n *\n * @param result - The value diff result containing summary data\n * @param options - Configuration options including params\n * @returns Grid columns and rows ready for ScreenshotDataGrid\n *\n * @example\n * const { columns, rows } = toValueDataGrid(result, {\n * params: run.params,\n * });\n */\nexport function toValueDataGrid(\n result: ValueDiffResult,\n options: ValueDataGridOptions,\n): ValueDataGridResult {\n const { params } = options;\n\n // Extract primary keys as array\n const primaryKeys = Array.isArray(params.primary_key)\n ? params.primary_key\n : [params.primary_key];\n\n // Apply schema to DataFrame for proper type handling\n // This ensures dataFrameToRowObjects has type information available\n const dataFrameWithSchema: DataFrame = {\n ...result.data,\n columns: VALUE_DIFF_SUMMARY_SCHEMA,\n };\n\n // Build columns using render functions from valueDiffCells\n const columns = createColumnDefinitions(primaryKeys, params);\n\n // Convert DataFrame to row objects\n const rows = dataFrameToRowObjects(dataFrameWithSchema);\n\n return { columns, rows };\n}\n","/**\n * @file dataGridFactory.ts\n * @description Abstract factory for creating data grid configurations\n *\n * This file provides a unified interface for generating grid data\n * for different run types (query, query_diff, value_diff, value_diff_detail, profile, profile_diff)\n *\n * It wraps the existing implementations:\n * - toDataGrid for single DataFrame display\n * - toDataDiffGrid for comparing two DataFrames\n * - toValueDiffGrid for joined diff data (with in_a/in_b columns)\n * - toValueDataGrid for value_diff summary (column match statistics)\n */\n\nimport Box from \"@mui/material/Box\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport type {\n ColDef,\n ColGroupDef,\n ICellRendererParams,\n} from \"ag-grid-community\";\nimport type { QueryDiffResult } from \"../../../api/adhocQuery\";\nimport type { NodeData } from \"../../../api/info\";\nimport type { ProfileDiffResult } from \"../../../api/profile\";\nimport type { RowCountDiffResult, RowCountResult } from \"../../../api/rowcount\";\nimport type { Run } from \"../../../api/types\";\nimport {\n type ColumnRenderMode,\n type ColumnType,\n type DataFrame,\n isProfileDiffRun,\n isProfileRun,\n isQueryBaseRun,\n isQueryDiffRun,\n isQueryRun,\n isRowCountDiffRun,\n isRowCountRun,\n isValueDiffDetailRun,\n isValueDiffRun,\n type RowObjectType,\n} from \"../../../api/types\";\nimport type { ValueDiffParams, ValueDiffResult } from \"../../../api/valuediff\";\nimport {\n mergeColumns,\n type SchemaDataGridOptions,\n type SchemaDataGridResult,\n type SingleEnvSchemaDataGridResult,\n toSchemaDataGrid,\n toSingleEnvDataGrid,\n} from \"../../../lib/dataGrid/generators/toSchemaDataGrid\";\n// Import existing implementations from @datarecce/ui\nimport {\n toDataDiffGridConfigured as toDataDiffGrid,\n toDataGridConfigured as toDataGrid,\n toRowCountDataGrid,\n toRowCountDiffDataGrid,\n toValueDiffGridConfigured as toValueDiffGrid,\n} from \"../../../utils/dataGrid\";\nimport { buildColumnTooltip, DataTypeIcon } from \"../DataTypeIcon\";\nimport { toValueDataGrid } from \"./generators/toValueDataGrid\";\n\n// ============================================================================\n// Types & Interfaces\n// ============================================================================\n\n/**\n * Common options shared across all grid types\n */\nexport interface BaseGridOptions {\n primaryKeys?: string[];\n pinnedColumns?: string[];\n columnsRenderMode?: Record<string, ColumnRenderMode>;\n onPrimaryKeyChange?: (primaryKeys: string[]) => void;\n onPinnedColumnsChange?: (pinnedColumns: string[]) => void;\n onColumnsRenderModeChanged?: (cols: Record<string, ColumnRenderMode>) => void;\n}\n\n/**\n * Additional options for diff grids\n */\nexport interface DiffGridOptions extends BaseGridOptions {\n changedOnly?: boolean;\n displayMode?: \"inline\" | \"side_by_side\";\n baseTitle?: string;\n currentTitle?: string;\n}\n\n/**\n * Standard output structure for all grid generation functions\n */\nexport interface DataGridResult {\n columns: ((ColDef<RowObjectType> | ColGroupDef<RowObjectType>) & {\n columnType?: ColumnType;\n columnRenderMode?: ColumnRenderMode;\n })[];\n rows: RowObjectType[];\n invalidPKeyBase?: boolean;\n invalidPKeyCurrent?: boolean;\n}\n\n/**\n * Discriminated union for run result data\n */\ntype RunResultData =\n | { kind: \"query\"; result: DataFrame }\n | { kind: \"query_diff_separate\"; result: QueryDiffResult }\n | {\n kind: \"query_diff_joined\";\n result: QueryDiffResult;\n primaryKeys: string[];\n }\n | { kind: \"value_diff\"; result: ValueDiffResult; params: ValueDiffParams }\n | { kind: \"value_diff_detail\"; result: DataFrame; primaryKeys: string[] }\n | { kind: \"profile\"; result: ProfileDiffResult }\n | { kind: \"profile_diff\"; result: ProfileDiffResult }\n // NEW: Add row count types\n | { kind: \"row_count\"; result: RowCountResult }\n | { kind: \"row_count_diff\"; result: RowCountDiffResult };\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Determines the appropriate grid generation strategy based on run type and data\n */\nfunction determineDataKind(run: Run): RunResultData | null {\n if (isQueryRun(run) || isQueryBaseRun(run)) {\n if (!run.result) return null;\n return { kind: \"query\", result: run.result };\n }\n\n if (isQueryDiffRun(run)) {\n if (!run.result) return null;\n // If the result has a `diff` field, it's pre-joined data\n if (run.result.diff && run.params?.primary_keys) {\n return {\n kind: \"query_diff_joined\",\n result: run.result,\n primaryKeys: run.params.primary_keys,\n };\n }\n // Otherwise, it's separate base/current DataFrames\n return { kind: \"query_diff_separate\", result: run.result };\n }\n\n if (isValueDiffRun(run)) {\n if (!run.result || !run.params) return null;\n return {\n kind: \"value_diff\",\n result: run.result,\n params: run.params,\n };\n }\n\n if (isValueDiffDetailRun(run)) {\n if (!run.result || !run.params?.primary_key) return null;\n const primaryKey = run.params.primary_key;\n const primaryKeys = Array.isArray(primaryKey) ? primaryKey : [primaryKey];\n return {\n kind: \"value_diff_detail\",\n result: run.result,\n primaryKeys,\n };\n }\n\n if (isProfileRun(run)) {\n if (!run.result?.current) return null;\n return { kind: \"profile\", result: run.result };\n }\n\n if (isProfileDiffRun(run)) {\n if (!run.result) return null;\n return { kind: \"profile_diff\", result: run.result };\n }\n\n if (isRowCountRun(run)) {\n if (!run.result) return null;\n return { kind: \"row_count\", result: run.result };\n }\n\n if (isRowCountDiffRun(run)) {\n if (!run.result) return null;\n return { kind: \"row_count_diff\", result: run.result };\n }\n\n return null;\n}\n\n/**\n * Cell renderer for column_name in single profile and side-by-side diff children.\n * Shows the column name + DataTypeIcon inline.\n */\nfunction profileColumnNameRenderer(params: ICellRendererParams<RowObjectType>) {\n const row = params.data;\n if (!row) return null;\n const name = params.value ? String(params.value) : \"\";\n const dataType = row.data_type ? String(row.data_type) : undefined;\n const tooltipText = buildColumnTooltip({ name, currentType: dataType });\n\n return (\n <Tooltip title={tooltipText} placement=\"top\">\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"6px\",\n overflow: \"hidden\",\n height: \"100%\",\n }}\n >\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {name}\n </Box>\n {dataType && <DataTypeIcon type={dataType} size={20} disableTooltip />}\n </Box>\n </Tooltip>\n );\n}\n\n/**\n * Cell renderer for column_name in inline diff mode.\n * Reads base/current data types from prefixed keys and renders DataTypeIcon(s).\n */\nfunction profileDiffColumnNameRenderer(\n params: ICellRendererParams<RowObjectType>,\n) {\n const row = params.data;\n if (!row) return null;\n const name = params.value ? String(params.value) : \"\";\n const baseType = row.base__data_type\n ? String(row.base__data_type)\n : undefined;\n const currentType = row.current__data_type\n ? String(row.current__data_type)\n : undefined;\n const isTypeChanged =\n baseType != null && currentType != null && baseType !== currentType;\n const displayType = currentType ?? baseType;\n const tooltipText = buildColumnTooltip({ name, baseType, currentType });\n\n return (\n <Tooltip title={tooltipText} placement=\"top\">\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"6px\",\n overflow: \"hidden\",\n height: \"100%\",\n }}\n >\n <Box\n sx={{\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {name}\n </Box>\n {isTypeChanged ? (\n <Box\n component=\"span\"\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"2px\",\n }}\n >\n <Box\n component=\"span\"\n sx={{ textDecoration: \"line-through\", opacity: 0.6 }}\n >\n <DataTypeIcon type={baseType} size={20} disableTooltip />\n </Box>\n <Box component=\"span\" sx={{ fontSize: \"0.7em\", opacity: 0.5 }}>\n →\n </Box>\n <DataTypeIcon type={currentType} size={20} disableTooltip />\n </Box>\n ) : (\n displayType && (\n <DataTypeIcon type={displayType} size={20} disableTooltip />\n )\n )}\n </Box>\n </Tooltip>\n );\n}\n\n/**\n * Checks if a column field name represents a data_type column.\n * Matches \"data_type\", \"base__data_type\", and \"current__data_type\".\n */\nfunction isDataTypeField(field: string | undefined): boolean {\n if (!field) return false;\n const lower = field.toLowerCase();\n return (\n lower === \"data_type\" ||\n lower === \"base__data_type\" ||\n lower === \"current__data_type\"\n );\n}\n\n/**\n * Post-processes profile grid columns to:\n * 1. Remove data_type columns (flat and side-by-side children)\n * 2. Inject a custom cell renderer on column_name that shows name + DataTypeIcon inline\n */\nfunction injectProfileColumnNameRenderer(\n result: DataGridResult,\n): DataGridResult {\n const isInlineDiff =\n result.rows.length > 0 && Object.hasOwn(result.rows[0], \"base__data_type\");\n\n const columns = result.columns\n .filter((col) => {\n // Remove data_type columns from ColGroupDef children (side-by-side mode)\n if (\"children\" in col && col.children) {\n const filtered = col.children.filter(\n (child) => !isDataTypeField((child as ColDef<RowObjectType>).field),\n );\n if (filtered.length === 0) return false;\n (col as ColGroupDef<RowObjectType>).children = filtered;\n return true;\n }\n // Remove flat data_type columns\n return !isDataTypeField((col as ColDef<RowObjectType>).field);\n })\n .map((col) => {\n // Inject renderer on column_name columns\n if (\"children\" in col && col.children) {\n return {\n ...col,\n children: col.children.map((child) => {\n const childCol = child as ColDef<RowObjectType>;\n if (childCol.field?.toLowerCase() === \"column_name\") {\n return {\n ...childCol,\n cellRenderer: profileColumnNameRenderer,\n };\n }\n return child;\n }),\n };\n }\n const colDef = col as ColDef<RowObjectType>;\n if (colDef.field?.toLowerCase() === \"column_name\") {\n return {\n ...colDef,\n cellRenderer: isInlineDiff\n ? profileDiffColumnNameRenderer\n : profileColumnNameRenderer,\n };\n }\n return col;\n });\n\n return { ...result, columns };\n}\n\n/**\n * Extracts the primary key field name from profile data\n */\nfunction getProfilePrimaryKey(result: ProfileDiffResult): string {\n const field = result.current?.columns.find(\n (f) => f.name.toLowerCase() === \"column_name\",\n );\n return field?.name ?? \"column_name\";\n}\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\n/**\n * Creates grid data from a Run object and options\n *\n * This is the main entry point that abstracts away the different\n * data transformations needed for different run types.\n *\n * @param run - The Run object containing result data\n * @param options - Grid configuration options\n * @returns DataGridResult with columns and rows, or null if invalid\n *\n * @example\n * ```tsx\n * const gridData = createDataGrid(run, {\n * primaryKeys: ['id'],\n * pinnedColumns: ['name'],\n * changedOnly: true,\n * displayMode: 'inline',\n * });\n *\n * if (gridData) {\n * return <ScreenshotDataGrid columns={gridData.columns} rows={gridData.rows} />;\n * }\n * ```\n */\nexport function createDataGrid(\n run: Run,\n options: DiffGridOptions = {},\n): DataGridResult | null {\n const dataKind = determineDataKind(run);\n if (!dataKind) return null;\n\n switch (dataKind.kind) {\n case \"query\":\n return toDataGrid(dataKind.result, {\n primaryKeys: options.primaryKeys,\n pinnedColumns: options.pinnedColumns,\n columnsRenderMode: options.columnsRenderMode,\n onPrimaryKeyChange: options.onPrimaryKeyChange,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n });\n\n case \"query_diff_separate\":\n return toDataDiffGrid(\n dataKind.result.base,\n dataKind.result.current,\n options,\n );\n\n case \"query_diff_joined\":\n if (!dataKind.result.diff) {\n return null;\n }\n return toValueDiffGrid(dataKind.result.diff, dataKind.primaryKeys, {\n changedOnly: options.changedOnly,\n pinnedColumns: options.pinnedColumns,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n baseTitle: options.baseTitle,\n currentTitle: options.currentTitle,\n displayMode: options.displayMode,\n columnsRenderMode: options.columnsRenderMode,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n });\n\n case \"value_diff\":\n return toValueDataGrid(dataKind.result, { params: dataKind.params });\n\n case \"value_diff_detail\":\n return toValueDiffGrid(dataKind.result, dataKind.primaryKeys, {\n changedOnly: options.changedOnly,\n pinnedColumns: options.pinnedColumns,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n displayMode: options.displayMode,\n columnsRenderMode: options.columnsRenderMode,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n });\n\n case \"profile\": {\n if (!dataKind.result.current) {\n return null;\n }\n const primaryKey = getProfilePrimaryKey(dataKind.result);\n const profileResult = toDataGrid(dataKind.result.current, {\n primaryKeys: [primaryKey],\n pinnedColumns: options.pinnedColumns,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n columnsRenderMode: options.columnsRenderMode,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n });\n return injectProfileColumnNameRenderer(profileResult);\n }\n\n case \"profile_diff\": {\n const primaryKey = getProfilePrimaryKey(dataKind.result);\n const profileDiffResult = toDataDiffGrid(\n dataKind.result.base,\n dataKind.result.current,\n {\n primaryKeys: [primaryKey],\n pinnedColumns: options.pinnedColumns,\n onPinnedColumnsChange: options.onPinnedColumnsChange,\n displayMode: options.displayMode,\n columnsRenderMode: options.columnsRenderMode,\n onColumnsRenderModeChanged: options.onColumnsRenderModeChanged,\n },\n );\n return injectProfileColumnNameRenderer(profileDiffResult);\n }\n\n case \"row_count\":\n return toRowCountDataGrid(dataKind.result);\n\n case \"row_count_diff\":\n return toRowCountDiffDataGrid(dataKind.result);\n\n default:\n return null;\n }\n}\n\n/**\n * Input types for the data-only factory function\n */\nexport type DataGridInput =\n | { type: \"single\"; dataframe: DataFrame }\n | { type: \"dual\"; base?: DataFrame; current?: DataFrame }\n | { type: \"joined\"; dataframe: DataFrame; primaryKeys: string[] }\n | {\n type: \"schema_diff\";\n base?: NodeData[\"columns\"];\n current?: NodeData[\"columns\"];\n }\n | { type: \"schema_single\"; columns?: NodeData[\"columns\"] };\n\n/**\n * Union of all possible grid results from createDataGridFromData\n */\nexport type DataGridFromDataResult =\n | DataGridResult\n | SchemaDataGridResult\n | SingleEnvSchemaDataGridResult;\n\n/**\n * Alternative factory that accepts raw data instead of Run objects\n * Useful for testing or when you have data outside the Run structure\n *\n * @overload For DataFrame inputs, returns DataGridResult\n * @overload For schema inputs, returns schema-specific result types\n */\nexport function createDataGridFromData(\n input:\n | { type: \"joined\"; dataframe: DataFrame; primaryKeys: string[] }\n | { type: \"dual\"; base?: DataFrame; current?: DataFrame }\n | { type: \"single\"; dataframe: DataFrame },\n options?: DiffGridOptions,\n): DataGridResult;\nexport function createDataGridFromData(\n input: {\n type: \"schema_diff\";\n base?: NodeData[\"columns\"];\n current?: NodeData[\"columns\"];\n },\n options?: SchemaDataGridOptions,\n): SchemaDataGridResult;\nexport function createDataGridFromData(\n input: { type: \"schema_single\"; columns?: NodeData[\"columns\"] },\n options?: SchemaDataGridOptions,\n): SingleEnvSchemaDataGridResult;\nexport function createDataGridFromData(\n input: DataGridInput,\n options?: DiffGridOptions | SchemaDataGridOptions,\n): DataGridFromDataResult {\n switch (input.type) {\n case \"single\":\n return toDataGrid(input.dataframe, (options ?? {}) as DiffGridOptions);\n\n case \"dual\":\n return toDataDiffGrid(\n input.base,\n input.current,\n (options ?? {}) as DiffGridOptions,\n );\n\n case \"joined\":\n return toValueDiffGrid(\n input.dataframe,\n input.primaryKeys,\n (options ?? {}) as DiffGridOptions,\n );\n\n case \"schema_diff\": {\n const schemaDiff = mergeColumns(input.base, input.current);\n return toSchemaDataGrid(\n schemaDiff,\n (options ?? {}) as SchemaDataGridOptions,\n );\n }\n\n case \"schema_single\":\n return toSingleEnvDataGrid(\n input.columns,\n (options ?? {}) as SchemaDataGridOptions,\n );\n }\n}\n","\"use client\";\n\n/**\n * @file ProfileDiffForm.tsx\n * @description Form component for configuring profile diff parameters.\n *\n * This component allows users to:\n * - View the model being profiled\n * - Select specific columns to profile (or all columns)\n *\n * Uses the useModelColumns hook from @datarecce/ui/hooks to fetch column metadata.\n */\n\nimport Autocomplete from \"@mui/material/Autocomplete\";\nimport Box from \"@mui/material/Box\";\nimport Checkbox from \"@mui/material/Checkbox\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport Typography from \"@mui/material/Typography\";\nimport { useEffect, useState } from \"react\";\nimport { useModelColumns } from \"../../hooks\";\nimport type { RunFormProps } from \"../run\";\n\nexport interface ProfileDiffFormParams {\n model: string;\n primary_key?: string | (string | undefined)[];\n columns?: string[];\n}\n\ntype ProfileDiffFormProp = RunFormProps<ProfileDiffFormParams>;\n\nexport function ProfileDiffForm({\n params,\n onParamsChanged,\n setIsReadyToExecute,\n}: ProfileDiffFormProp) {\n const [allColumns, setAllColumns] = useState<boolean>(\n !params.columns || params.columns.length === 0,\n );\n\n const model = params.model;\n\n const { columns, isLoading, error } = useModelColumns(params.model);\n\n useEffect(() => {\n setIsReadyToExecute(!!model);\n }, [model, setIsReadyToExecute]);\n\n const columnNames = columns.map((c) => c.name);\n\n if (isLoading) {\n return <Box>Loading...</Box>;\n }\n\n if (columnNames.length === 0 || error) {\n return (\n <Box>\n Error: Please provide the 'catalog.json' to list column\n candidates\n </Box>\n );\n }\n\n return (\n <Stack spacing={5} sx={{ m: \"8px 24px\", pb: \"200px\" }}>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Model\n </Typography>\n <TextField\n fullWidth\n size=\"small\"\n value={params.model}\n slotProps={{ input: { readOnly: true } }}\n />\n </Box>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Columns\n </Typography>\n <FormControlLabel\n control={\n <Checkbox\n checked={allColumns}\n onChange={(e) => {\n setAllColumns(e.target.checked);\n onParamsChanged({\n ...params,\n columns: undefined,\n });\n }}\n size=\"small\"\n />\n }\n label=\"All columns\"\n sx={{ mb: \"10px\" }}\n />\n {!allColumns && (\n <Autocomplete\n multiple\n size=\"small\"\n disableCloseOnSelect\n options={columnNames}\n value={params.columns ?? []}\n onChange={(_, newValue) => {\n onParamsChanged({\n ...params,\n columns: newValue.length === 0 ? undefined : newValue,\n });\n }}\n renderInput={(inputProps) => (\n <TextField\n {...inputProps}\n placeholder={\n (params.columns ?? []).length === 0 ? \"Select columns\" : \"\"\n }\n className=\"no-track-pii-safe\"\n />\n )}\n />\n )}\n </Box>\n </Stack>\n );\n}\n","\"use client\";\n\n/**\n * @file ProfileResultView.tsx\n * @description Framework-agnostic Profile result view components for @datarecce/ui\n *\n * These components use the createResultView factory pattern and can be used by both\n * Recce OSS and Recce Cloud. They accept generic Run types and use type guards\n * for validation.\n *\n * The components display profile data in a grid format:\n * - ProfileResultView: Single environment profile stats (column metrics)\n * - ProfileDiffResultView: Diff between environments (base vs current column metrics)\n *\n * OSS-specific features (like RunToolbar, DiffDisplayModeSwitch) should be injected\n * via the header prop or by wrapping these components.\n */\n\nimport type { ReactNode } from \"react\";\nimport {\n type ColumnRenderMode,\n isProfileDiffRun,\n isProfileRun,\n type ProfileDiffResult,\n type ProfileDiffViewOptions,\n type Run,\n} from \"../../api\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps } from \"../result/types\";\nimport { RunToolbar } from \"../run/RunToolbar\";\nimport { DiffDisplayModeSwitch } from \"../ui/DiffDisplayModeSwitch\";\nimport { createDataGrid } from \"../ui/dataGrid/dataGridFactory\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with profile result (single environment)\n */\nexport type ProfileRun = Run & {\n type: \"profile\";\n result?: ProfileDiffResult;\n};\n\n/**\n * Run type with profile_diff result\n */\nexport type ProfileDiffRun = Run & {\n type: \"profile_diff\";\n result?: ProfileDiffResult;\n};\n\n/**\n * Props for ProfileResultView components\n */\nexport interface ProfileResultViewProps\n extends CreatedResultViewProps<ProfileDiffViewOptions> {\n run: ProfileRun | ProfileDiffRun | unknown;\n /**\n * Optional header element to render above the grid.\n * Use this to inject OSS-specific controls like RunToolbar.\n */\n header?: ReactNode;\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n// Type guard wrapper for factory compatibility (accepts unknown instead of Run)\ntype ProfileDiffRunInternal = Extract<\n Parameters<typeof isProfileDiffRun>[0],\n { type: \"profile_diff\" }\n>;\n\nconst isProfileDiffRunGuard = (run: unknown): run is ProfileDiffRunInternal =>\n typeof run === \"object\" &&\n run !== null &&\n \"type\" in run &&\n isProfileDiffRun(run as Parameters<typeof isProfileDiffRun>[0]);\n\ntype ProfileRunInternal = Extract<\n Parameters<typeof isProfileRun>[0],\n { type: \"profile\" }\n>;\n\nconst isProfileRunGuard = (run: unknown): run is ProfileRunInternal =>\n typeof run === \"object\" &&\n run !== null &&\n \"type\" in run &&\n isProfileRun(run as Parameters<typeof isProfileRun>[0]);\n\n// ============================================================================\n// Factory-Created Components\n// ============================================================================\n\n/**\n * Result view for single environment profile stats\n *\n * Displays a grid with column metrics (count, distinct, null proportion, etc.)\n * for a single dbt model.\n *\n * @example\n * ```tsx\n * <ProfileResultView run={profileRun} ref={gridRef} />\n * ```\n */\nexport const ProfileResultView = createResultView<\n ProfileRunInternal,\n ProfileDiffViewOptions\n>({\n displayName: \"ProfileResultView\",\n typeGuard: isProfileRunGuard,\n expectedRunType: \"profile\",\n screenshotWrapper: \"grid\",\n transformData: (run, { viewOptions, onViewOptionsChanged }) => {\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n\n // Default proportion columns to percentage display\n const columnsRenderMode = {\n distinct_proportion: \"percent\" as ColumnRenderMode,\n not_null_proportion: \"percent\" as ColumnRenderMode,\n ...viewOptions?.columnsRenderMode,\n };\n\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n const handlePinnedColumnsChanged = (newPinnedColumns: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: newPinnedColumns,\n });\n }\n };\n\n const gridData = createDataGrid(run, {\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n }) ?? { columns: [], rows: [] };\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n isEmpty: gridData.columns.length === 0,\n };\n },\n});\n\n/**\n * Result view for comparing profile stats between base and current environments\n *\n * Displays a grid with column metrics from both environments,\n * styled to highlight differences.\n *\n * @example\n * ```tsx\n * <ProfileDiffResultView\n * run={profileDiffRun}\n * ref={gridRef}\n * viewOptions={{ display_mode: \"inline\" }}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const ProfileDiffResultView = createResultView<\n ProfileDiffRunInternal,\n ProfileDiffViewOptions\n>({\n displayName: \"ProfileDiffResultView\",\n typeGuard: isProfileDiffRunGuard,\n expectedRunType: \"profile_diff\",\n screenshotWrapper: \"grid\",\n transformData: (run, { viewOptions, onViewOptionsChanged }) => {\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n const displayMode = viewOptions?.display_mode ?? \"inline\";\n\n // Default proportion columns to percentage display\n const columnsRenderMode = {\n distinct_proportion: \"percent\" as ColumnRenderMode,\n not_null_proportion: \"percent\" as ColumnRenderMode,\n ...viewOptions?.columnsRenderMode,\n };\n\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n const handlePinnedColumnsChanged = (newPinnedColumns: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: newPinnedColumns,\n });\n }\n };\n\n const gridData = createDataGrid(run, {\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n displayMode,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n }) ?? { columns: [], rows: [] };\n\n // OSS-specific header with RunToolbar and DiffDisplayModeSwitch\n const header = (\n <RunToolbar>\n <DiffDisplayModeSwitch\n displayMode={displayMode}\n onDisplayModeChanged={(mode) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n display_mode: mode,\n });\n }\n }}\n />\n </RunToolbar>\n );\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n header,\n isEmpty: gridData.columns.length === 0,\n };\n },\n});\n","\"use client\";\n\n/**\n * @file QueryDiffResultView.tsx\n * @description Framework-agnostic Query Diff result view for @datarecce/ui\n *\n * Handles both JOIN and non-JOIN modes:\n * - JOIN mode: Server computes the diff, result has `run.result.diff`\n * - Non-JOIN mode: Client-side diff, result has `run.result.base` and `run.result.current`\n *\n * Features:\n * - Displays row-level diff data with changed highlighting\n * - \"Changed only\" filter to show only differing rows\n * - Side-by-side vs inline display mode toggle\n * - Column pinning support\n * - Primary key selection (non-JOIN mode)\n * - Warning for truncated results\n * - Warning for non-unique primary keys (non-JOIN mode)\n */\n\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport type { Run } from \"../../api\";\nimport {\n type ColumnRenderMode,\n isQueryDiffRun,\n type QueryDiffViewOptions,\n type QueryPreviewChangeParams,\n} from \"../../api\";\nimport {\n toDataDiffGridConfigured,\n toValueDiffGridConfigured,\n} from \"../../utils\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\nimport { ChangedOnlyCheckbox } from \"../ui/ChangedOnlyCheckbox\";\nimport { DiffDisplayModeSwitch } from \"../ui/DiffDisplayModeSwitch\";\n\nimport \"./styles.css\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype QueryDiffRun = Extract<Run, { type: \"query_diff\" }>;\n\n/**\n * Props for QueryDiffResultView component\n */\nexport interface QueryDiffResultViewProps\n extends CreatedResultViewProps<QueryDiffViewOptions> {\n run: QueryDiffRun | unknown;\n}\n\n/**\n * Type guard wrapper that accepts unknown and delegates to typed guard.\n */\nfunction isQueryDiffRunGuard(run: unknown): run is QueryDiffRun {\n return isQueryDiffRun(run as Run);\n}\n\n/**\n * QueryDiffResultView component - displays query diff results in a data grid.\n *\n * Handles both JOIN and non-JOIN modes:\n * - JOIN mode: Server computes the diff, result has `run.result.diff`\n * - Non-JOIN mode: Client-side diff, result has `run.result.base` and `run.result.current`\n *\n * Key differences between modes:\n * - Primary key handling: only in non-JOIN mode (server handles it in JOIN mode)\n * - Warning sources: `diff.limit/more` vs `current.limit/more || base.more`\n * - \"No change\" empty state: only in JOIN mode with changedOnly=true\n *\n * Features:\n * - Displays row-level diff data with changed highlighting\n * - \"Changed only\" filter to show only differing rows\n * - Side-by-side vs inline display mode toggle\n * - Column pinning support\n * - Primary key selection (non-JOIN mode)\n * - Warning for truncated results\n * - Warning for non-unique primary keys (non-JOIN mode)\n *\n * @example\n * ```tsx\n * <QueryDiffResultView\n * run={queryDiffRun}\n * viewOptions={{ changed_only: true, display_mode: 'inline' }}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const QueryDiffResultView = createResultView<\n QueryDiffRun,\n QueryDiffViewOptions,\n DataGridHandle\n>({\n displayName: \"QueryDiffResultView\",\n typeGuard: isQueryDiffRunGuard,\n expectedRunType: \"query_diff\",\n screenshotWrapper: \"grid\",\n emptyState: \"No data\",\n transformData: (\n run,\n { viewOptions, onViewOptionsChanged },\n ): ResultViewData | null => {\n // Determine mode based on result structure\n const isJoinMode =\n run.result && \"diff\" in run.result && run.result.diff != null;\n\n // Compute baseTitle/currentTitle for sandbox editor\n let baseTitle: string | undefined;\n let currentTitle: string | undefined;\n if (run.params && (run.params as QueryPreviewChangeParams).current_model) {\n baseTitle = \"Original\";\n currentTitle = \"Editor\";\n }\n\n // Extract view options with defaults\n const changedOnly = viewOptions?.changed_only ?? false;\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n const displayMode = viewOptions?.display_mode ?? \"inline\";\n const columnsRenderMode = viewOptions?.columnsRenderMode ?? {};\n\n // Primary keys only used in non-JOIN mode\n const primaryKeys = !isJoinMode ? (viewOptions?.primary_keys ?? []) : [];\n\n // Create callbacks for view option changes\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n // Primary key handler only for non-JOIN mode\n const handlePrimaryKeyChanged = !isJoinMode\n ? (pks: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n primary_keys: pks,\n });\n }\n }\n : undefined;\n\n const handlePinnedColumnsChanged = (pinnedCols: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: pinnedCols,\n });\n }\n };\n\n // Build grid data using appropriate grid generator based on mode\n let gridData: {\n columns: unknown[];\n rows: unknown[];\n invalidPKeyBase?: boolean;\n invalidPKeyCurrent?: boolean;\n };\n\n if (isJoinMode && run.result?.diff) {\n // JOIN mode: use toValueDiffGrid with the diff data\n const primaryKeysFromParams = run.params?.primary_keys ?? [];\n gridData = toValueDiffGridConfigured(\n run.result.diff,\n primaryKeysFromParams,\n {\n changedOnly,\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n baseTitle,\n currentTitle,\n displayMode,\n },\n );\n } else {\n // Non-JOIN mode: use toDataDiffGrid with base/current data\n gridData = toDataDiffGridConfigured(\n run.result?.base,\n run.result?.current,\n {\n changedOnly,\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n baseTitle,\n currentTitle,\n displayMode,\n primaryKeys,\n onPrimaryKeyChange: handlePrimaryKeyChanged,\n },\n );\n }\n\n // Build warnings array\n const warnings: string[] = [];\n\n // Primary key uniqueness warning - only for non-JOIN mode\n if (!isJoinMode && primaryKeys.length > 0) {\n const pkName = primaryKeys.join(\", \");\n\n if (gridData.invalidPKeyBase && gridData.invalidPKeyCurrent) {\n warnings.push(\n `Warning: The primary key '${pkName}' is not unique in the base and current environments`,\n );\n } else if (gridData.invalidPKeyBase) {\n warnings.push(\n `Warning: The primary key '${pkName}' is not unique in the base environment`,\n );\n } else if (gridData.invalidPKeyCurrent) {\n warnings.push(\n `Warning: The primary key '${pkName}' is not unique in the current environment`,\n );\n }\n }\n\n // Limit warning - different sources for JOIN vs non-JOIN\n const limit = isJoinMode\n ? (run.result?.diff?.limit ?? 0)\n : (run.result?.current?.limit ?? 0);\n\n const hasMore = isJoinMode\n ? run.result?.diff?.more\n : run.result?.current?.more || run.result?.base?.more;\n\n if (limit > 0 && hasMore) {\n warnings.push(\n `Warning: Displayed results are limited to ${limit.toLocaleString()} records. To ensure complete data retrieval, consider applying a LIMIT or WHERE clause to constrain the result set.`,\n );\n }\n\n // Empty state when no columns (no data at all)\n if (gridData.columns.length === 0) {\n return { isEmpty: true };\n }\n\n // Build toolbar with display mode switch and changed only checkbox\n const toolbar = (\n <>\n <DiffDisplayModeSwitch\n displayMode={displayMode}\n onDisplayModeChanged={(newDisplayMode) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n display_mode: newDisplayMode,\n });\n }\n }}\n />\n <ChangedOnlyCheckbox\n changedOnly={viewOptions?.changed_only}\n onChange={() => {\n const newChangedOnly = !viewOptions?.changed_only;\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n changed_only: newChangedOnly,\n });\n }\n }}\n />\n </>\n );\n\n // \"No change\" empty state - only for JOIN mode with changedOnly=true\n if (isJoinMode && changedOnly && gridData.rows.length === 0) {\n return {\n isEmpty: true,\n emptyMessage: \"No change\",\n toolbar,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n warnings: warnings.length > 0 ? warnings : undefined,\n toolbar,\n defaultColumnOptions: {\n resizable: true,\n maxWidth: 800,\n minWidth: 35,\n },\n noRowsMessage: \"No mismatched rows\",\n };\n },\n}) as ForwardRefExoticComponent<\n QueryDiffResultViewProps & RefAttributes<DataGridHandle>\n>;\n\n// Re-export the view options type for convenience\nexport type { QueryDiffViewOptions };\n","\"use client\";\n\n/**\n * @file QueryResultView.tsx\n * @description Framework-agnostic Query result view for @datarecce/ui\n *\n * Displays query results in a data grid format. Uses the createResultView\n * factory pattern and can be used by both Recce OSS and Recce Cloud.\n *\n * Features:\n * - Displays query results with column pinning support\n * - Shows amber warning when results are truncated (limit exceeded)\n * - Optional \"Add to Checklist\" button in toolbar\n * - Supports both \"query\" and \"query_base\" run types\n */\n\nimport Button from \"@mui/material/Button\";\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport type { Run } from \"../../api\";\nimport {\n type ColumnRenderMode,\n isQueryBaseRun,\n isQueryRun,\n type QueryViewOptions,\n} from \"../../api\";\nimport { toDataGridConfigured } from \"../../utils\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype QueryRun = Extract<Run, { type: \"query\" }>;\ntype QueryBaseRun = Extract<Run, { type: \"query_base\" }>;\n\n/**\n * Props for QueryResultView component\n */\nexport interface QueryResultViewProps\n extends CreatedResultViewProps<QueryViewOptions> {\n run: QueryRun | QueryBaseRun | unknown;\n}\n\n/**\n * Type guard for query or query_base runs.\n * QueryResultView accepts both run types.\n * Wrapper accepts unknown and delegates to typed guards.\n */\nfunction isQueryOrQueryBaseRun(run: unknown): run is QueryRun | QueryBaseRun {\n return isQueryRun(run as Run) || isQueryBaseRun(run as Run);\n}\n\n/**\n * QueryResultView component - displays query results in a data grid.\n *\n * Features:\n * - Displays query results with column pinning support\n * - Shows amber warning when results are truncated (limit exceeded)\n * - Optional \"Add to Checklist\" button in toolbar\n * - Supports both \"query\" and \"query_base\" run types\n *\n * @example\n * ```tsx\n * <QueryResultView\n * run={queryRun}\n * viewOptions={{ pinned_columns: ['id'] }}\n * onViewOptionsChanged={setViewOptions}\n * onAddToChecklist={(run) => addToChecklist(run)}\n * />\n * ```\n */\nexport const QueryResultView = createResultView<\n QueryRun | QueryBaseRun,\n QueryViewOptions,\n DataGridHandle\n>({\n displayName: \"QueryResultView\",\n typeGuard: isQueryOrQueryBaseRun,\n expectedRunType: \"query\",\n screenshotWrapper: \"grid\",\n emptyState: \"No data\",\n transformData: (\n run,\n { viewOptions, onViewOptionsChanged, onAddToChecklist },\n ): ResultViewData | null => {\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n const columnsRenderMode = viewOptions?.columnsRenderMode ?? {};\n\n // Create callbacks for view option changes\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n const handlePinnedColumnsChanged = (pinnedColumns: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: pinnedColumns,\n });\n }\n };\n\n // Build grid data using toDataGridConfigured\n if (!run.result) {\n return { isEmpty: true };\n }\n\n const gridData = toDataGridConfigured(run.result, {\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n });\n\n // Empty state when no columns\n if (gridData.columns.length === 0) {\n return { isEmpty: true };\n }\n\n // Build warnings array\n const dataframe = run.result;\n const limit = dataframe ? (dataframe.limit ?? 0) : 0;\n const warnings: string[] = [];\n if (limit > 0 && dataframe?.more) {\n warnings.push(\n `Warning: Displayed results are limited to ${limit.toLocaleString()} records. To ensure complete data retrieval, consider applying a LIMIT or WHERE clause to constrain the result set.`,\n );\n }\n\n // Build toolbar with \"Add to Checklist\" button\n const toolbar = onAddToChecklist ? (\n <Button\n sx={{ my: \"5px\" }}\n size=\"small\"\n color=\"iochmara\"\n variant=\"contained\"\n onClick={() => {\n onAddToChecklist(run);\n }}\n >\n Add to Checklist\n </Button>\n ) : undefined;\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n warnings: warnings.length > 0 ? warnings : undefined,\n warningStyle: \"amber\",\n toolbar,\n defaultColumnOptions: {\n resizable: true,\n maxWidth: 800,\n minWidth: 35,\n },\n };\n },\n}) as ForwardRefExoticComponent<\n QueryResultViewProps & RefAttributes<DataGridHandle>\n>;\n\n// Re-export the view options type for convenience\nexport type { QueryViewOptions };\n","\"use client\";\n\n/**\n * @file RowCountResultView.tsx\n * @description Framework-agnostic Row Count result view components for @datarecce/ui\n *\n * These components use the createResultView factory pattern and can be used by both\n * Recce OSS and Recce Cloud. They accept generic Run types and use type guards\n * for validation.\n *\n * The components display row count data in a grid format:\n * - RowCountResultView: Single environment row counts (name, count)\n * - RowCountDiffResultView: Diff between environments (name, base, current, delta)\n */\n\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n isRowCountDiffRun,\n isRowCountRun,\n type RowCountDiffResult,\n type RowCountResult,\n type Run,\n} from \"../../api\";\nimport { toRowCountDataGrid, toRowCountDiffDataGrid } from \"../../utils\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with row_count result\n */\nexport type RowCountRun = Run & {\n type: \"row_count\";\n result?: RowCountResult;\n};\n\n/**\n * Run type with row_count_diff result\n */\nexport type RowCountDiffRun = Run & {\n type: \"row_count_diff\";\n result?: RowCountDiffResult;\n};\n\n/**\n * Props for RowCountResultView components\n */\nexport interface RowCountResultViewProps\n extends CreatedResultViewProps<unknown> {\n run: RowCountRun | RowCountDiffRun | unknown;\n}\n\n// ============================================================================\n// Type Guards (wrapper to accept unknown)\n// ============================================================================\n\nfunction isRowCountRunGuard(run: unknown): run is RowCountRun {\n return isRowCountRun(run as Run);\n}\n\nfunction isRowCountDiffRunGuard(run: unknown): run is RowCountDiffRun {\n return isRowCountDiffRun(run as Run);\n}\n\n// ============================================================================\n// Transform Functions\n// ============================================================================\n\n/**\n * Transform RowCountRun data to grid format\n */\nfunction transformRowCountData(run: RowCountRun): ResultViewData | null {\n if (!run.result) {\n return null;\n }\n\n const gridData = toRowCountDataGrid(run.result);\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n isEmpty: gridData.rows.length === 0,\n };\n}\n\n/**\n * Transform RowCountDiffRun data to grid format\n */\nfunction transformRowCountDiffData(\n run: RowCountDiffRun,\n): ResultViewData | null {\n if (!run.result) {\n return null;\n }\n\n const gridData = toRowCountDiffDataGrid(run.result);\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n isEmpty: gridData.rows.length === 0,\n };\n}\n\n// ============================================================================\n// Factory-Created Components\n// ============================================================================\n\n/**\n * Result view for single environment row counts\n *\n * Displays a grid with model names and their row counts.\n *\n * @example\n * ```tsx\n * <RowCountResultView run={rowCountRun} ref={gridRef} />\n * ```\n */\nexport const RowCountResultView = createResultView<\n RowCountRun,\n unknown,\n DataGridHandle\n>({\n displayName: \"RowCountResultView\",\n typeGuard: isRowCountRunGuard,\n expectedRunType: \"row_count\",\n screenshotWrapper: \"grid\",\n transformData: transformRowCountData,\n emptyState: \"No nodes matched\",\n}) as ForwardRefExoticComponent<\n RowCountResultViewProps & RefAttributes<DataGridHandle>\n>;\n\n/**\n * Result view for comparing row counts between base and current environments\n *\n * Displays a grid with model names, base counts, current counts, and delta.\n * Cells are styled to indicate added (green) or removed (red) rows.\n *\n * @example\n * ```tsx\n * <RowCountDiffResultView run={rowCountDiffRun} ref={gridRef} />\n * ```\n */\nexport const RowCountDiffResultView = createResultView<\n RowCountDiffRun,\n unknown,\n DataGridHandle\n>({\n displayName: \"RowCountDiffResultView\",\n typeGuard: isRowCountDiffRunGuard,\n expectedRunType: \"row_count_diff\",\n screenshotWrapper: \"grid\",\n transformData: transformRowCountDiffData,\n emptyState: \"No nodes matched\",\n}) as ForwardRefExoticComponent<\n RowCountResultViewProps & RefAttributes<DataGridHandle>\n>;\n","\"use client\";\n\n/**\n * @file TopKDiffForm.tsx\n * @description Form component for Top-K diff parameters.\n *\n * This component allows users to select a column for top-K analysis.\n * It displays a dropdown of available columns from the model and\n * requires catalog.json to be available for column listing.\n */\n\nimport Box from \"@mui/material/Box\";\nimport FormControl from \"@mui/material/FormControl\";\nimport FormLabel from \"@mui/material/FormLabel\";\nimport NativeSelect from \"@mui/material/NativeSelect\";\nimport { useEffect } from \"react\";\nimport type { TopKDiffParams } from \"../../api\";\nimport { useModelColumns } from \"../../hooks\";\nimport type { RunFormProps } from \"../run\";\n\ntype TopKDiffFormProps = RunFormProps<TopKDiffParams>;\n\nexport function TopKDiffForm({\n params,\n onParamsChanged,\n setIsReadyToExecute,\n}: TopKDiffFormProps) {\n const { columns, isLoading, error } = useModelColumns(params.model);\n const columnNames = columns.map((c) => c.name);\n\n useEffect(() => {\n setIsReadyToExecute(!!params.column_name);\n }, [params, setIsReadyToExecute]);\n\n if (isLoading) {\n return <Box>Loading...</Box>;\n }\n\n if (columnNames.length === 0 || error) {\n return (\n <Box>\n Error: Please provide the 'catalog.json' to list column\n candidates\n </Box>\n );\n }\n\n return (\n <Box sx={{ m: \"16px\" }}>\n <FormControl fullWidth>\n <FormLabel sx={{ mb: 1 }}>Pick a column to show top-k</FormLabel>\n <NativeSelect\n value={params.column_name}\n onChange={(e) => {\n const column = e.target.value;\n onParamsChanged({ ...params, column_name: column });\n }}\n >\n <option value=\"\">Select column</option>\n {columnNames.map((c) => (\n <option key={c} value={c} className=\"no-track-pii-safe\">\n {c}\n </option>\n ))}\n </NativeSelect>\n </FormControl>\n </Box>\n );\n}\n","\"use client\";\n\n/**\n * @file TopKDiffResultView.tsx\n * @description Framework-agnostic Top-K diff result view component for @datarecce/ui\n *\n * This component uses the createResultView factory pattern and can be used by both\n * Recce OSS and Recce Cloud. It accepts generic Run types and uses type guards\n * for validation.\n *\n * The component displays top-K value distribution comparison:\n * - Horizontal bar chart comparing base vs current top-K values\n * - \"View More Items\" / \"View Only Top-10\" toggle when >10 items exist\n * - Title shows model name and column name\n * - Dark/light theme support\n */\n\nimport Box from \"@mui/material/Box\";\nimport Link from \"@mui/material/Link\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n isTopKDiffRun,\n type Run,\n type TopKDiffParams,\n type TopKDiffResult,\n type TopKViewOptions,\n} from \"../../api\";\nimport { useIsDark } from \"../../hooks\";\nimport { TopKBarChart } from \"../data/TopKBarChart\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with top_k_diff result\n */\nexport type TopKDiffRun = Run & {\n type: \"top_k_diff\";\n params?: TopKDiffParams;\n result?: TopKDiffResult;\n};\n\n/**\n * Props for TopKDiffResultView component\n */\nexport interface TopKDiffResultViewProps\n extends CreatedResultViewProps<TopKViewOptions> {\n run: TopKDiffRun | unknown;\n}\n\n// ============================================================================\n// Type Guard (wrapper to accept unknown)\n// ============================================================================\n\n/**\n * Type guard wrapper that accepts unknown and delegates to typed guard.\n */\nfunction isTopKDiffRunGuard(run: unknown): run is TopKDiffRun {\n return isTopKDiffRun(run as Run);\n}\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\n/**\n * Title component for the top-K chart.\n * Uses useIsDark hook for theme-aware styling.\n */\nfunction TopKTitle({\n model,\n columnName,\n}: {\n model: string;\n columnName: string;\n}) {\n const isDark = useIsDark();\n return (\n <Typography\n variant=\"h5\"\n sx={{\n pt: 4,\n textAlign: \"center\",\n color: isDark ? \"grey.200\" : \"grey.600\",\n }}\n >\n Model {model}.{columnName}\n </Typography>\n );\n}\n\n/**\n * View toggle link for switching between top-10 and all items.\n */\nfunction ViewToggleLink({\n showAll,\n onToggle,\n}: {\n showAll: boolean;\n onToggle: () => void;\n}) {\n return (\n <Box sx={{ display: \"flex\", p: 5, justifyContent: \"start\" }}>\n <Link\n component=\"button\"\n onClick={onToggle}\n sx={{ color: \"iochmara.main\", cursor: \"pointer\" }}\n >\n {showAll ? \"View Only Top-10\" : \"View More Items\"}\n </Link>\n </Box>\n );\n}\n\n// ============================================================================\n// Transform Function\n// ============================================================================\n\n/**\n * Transform TopKDiffRun data to result view format\n */\nfunction transformTopKDiffData(\n run: TopKDiffRun,\n {\n viewOptions,\n onViewOptionsChanged,\n }: {\n viewOptions?: TopKViewOptions;\n onViewOptionsChanged?: (options: TopKViewOptions) => void;\n },\n): ResultViewData | null {\n const result = run.result;\n const params = run.params as TopKDiffParams;\n\n // Empty state when no result\n if (!result) {\n return { isEmpty: true };\n }\n\n const baseTopK = result.base;\n const currentTopK = result.current;\n\n // Derive isDisplayTopTen from viewOptions (inverted: show_all=false means top10=true)\n const showAll = viewOptions?.show_all ?? false;\n const isDisplayTopTen = !showAll;\n\n // Check if toggle should be visible (>10 items in either base or current)\n const shouldShowToggle =\n baseTopK.values.length > 10 || currentTopK.values.length > 10;\n\n // Build footer with toggle link (if needed)\n const footer = shouldShowToggle ? (\n <ViewToggleLink\n showAll={showAll}\n onToggle={() => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n show_all: !showAll,\n });\n }\n }}\n />\n ) : undefined;\n\n return {\n content: (\n <>\n <TopKTitle model={params.model} columnName={params.column_name} />\n <Stack direction=\"row\" alignItems=\"center\">\n <Box sx={{ flex: 1 }} />\n <TopKBarChart\n baseData={baseTopK}\n currentData={currentTopK}\n showComparison={true}\n maxItems={isDisplayTopTen ? 10 : undefined}\n />\n <Box sx={{ flex: 1 }} />\n </Stack>\n </>\n ),\n footer,\n };\n}\n\n// ============================================================================\n// Factory-Created Component\n// ============================================================================\n\n/**\n * TopKDiffResultView component - displays top-K value distribution comparison.\n *\n * Features:\n * - Displays horizontal bar chart comparing base vs current top-K values\n * - \"View More Items\" / \"View Only Top-10\" toggle when >10 items exist\n * - Title shows model name and column name\n * - Dark/light theme support\n *\n * @example\n * ```tsx\n * <TopKDiffResultView\n * run={topKDiffRun}\n * viewOptions={{ show_all: false }}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const TopKDiffResultView = createResultView<\n TopKDiffRun,\n TopKViewOptions,\n HTMLDivElement\n>({\n displayName: \"TopKDiffResultView\",\n typeGuard: isTopKDiffRunGuard,\n expectedRunType: \"top_k_diff\",\n screenshotWrapper: \"box\",\n emptyState: \"No data\",\n transformData: transformTopKDiffData,\n}) as ForwardRefExoticComponent<\n TopKDiffResultViewProps & RefAttributes<HTMLDivElement>\n>;\n","\"use client\";\n\n/**\n * @file ValueDiffDetailResultView.tsx\n * @description Framework-agnostic Value Diff Detail result view for @datarecce/ui\n *\n * Displays row-level value diff data in a data grid format. Uses the createResultView\n * factory pattern and can be used by both Recce OSS and Recce Cloud.\n *\n * Features:\n * - Displays row-level diff data with changed highlighting\n * - \"Changed only\" filter to show only differing rows\n * - Side-by-side vs inline display mode toggle\n * - Column pinning support\n * - Shows amber warning when results are truncated\n * - Toolbar-in-empty-state pattern: shows \"No change\" when changed_only=true but no changes\n */\n\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n type ColumnRenderMode,\n isValueDiffDetailRun,\n type Run,\n type ValueDiffDetailViewOptions,\n} from \"../../api\";\nimport { toValueDiffGridConfigured } from \"../../utils\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\nimport { ChangedOnlyCheckbox } from \"../ui/ChangedOnlyCheckbox\";\nimport { DiffDisplayModeSwitch } from \"../ui/DiffDisplayModeSwitch\";\n\n// Import AG Grid styles for context menu visibility\nimport \"../data/agGridStyles.css\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with value_diff_detail result\n */\nexport type ValueDiffDetailRun = Run & {\n type: \"value_diff_detail\";\n};\n\n/**\n * Props for ValueDiffDetailResultView component\n */\nexport interface ValueDiffDetailResultViewProps\n extends CreatedResultViewProps<ValueDiffDetailViewOptions> {\n run: ValueDiffDetailRun | unknown;\n}\n\n// ============================================================================\n// Type Guard\n// ============================================================================\n\n/**\n * Type guard wrapper that accepts unknown and delegates to typed guard.\n */\nfunction isValueDiffDetailRunGuard(run: unknown): run is ValueDiffDetailRun {\n return isValueDiffDetailRun(run as Run);\n}\n\n// ============================================================================\n// Factory-Created Component\n// ============================================================================\n\n/**\n * ValueDiffDetailResultView component - displays value diff details in a data grid.\n *\n * Features:\n * - Displays row-level diff data with changed highlighting\n * - \"Changed only\" filter to show only differing rows\n * - Side-by-side vs inline display mode toggle\n * - Column pinning support\n * - Shows amber warning when results are truncated\n * - Toolbar-in-empty-state pattern: shows \"No change\" when changed_only=true but no changes\n *\n * @example\n * ```tsx\n * <ValueDiffDetailResultView\n * run={valueDiffDetailRun}\n * viewOptions={{ changed_only: true, display_mode: 'inline' }}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const ValueDiffDetailResultView = createResultView<\n ValueDiffDetailRun,\n ValueDiffDetailViewOptions,\n DataGridHandle\n>({\n displayName: \"ValueDiffDetailResultView\",\n typeGuard: isValueDiffDetailRunGuard,\n expectedRunType: \"value_diff_detail\",\n screenshotWrapper: \"grid\",\n emptyState: \"No data\",\n transformData: (\n run,\n { viewOptions, onViewOptionsChanged },\n ): ResultViewData | null => {\n const changedOnly = viewOptions?.changed_only ?? false;\n const pinnedColumns = viewOptions?.pinned_columns ?? [];\n const displayMode = viewOptions?.display_mode ?? \"inline\";\n const columnsRenderMode = viewOptions?.columnsRenderMode ?? {};\n\n // Extract primary keys from params\n const primaryKey = run.params?.primary_key;\n if (!primaryKey || !run.result) {\n return { isEmpty: true };\n }\n const primaryKeys = Array.isArray(primaryKey) ? primaryKey : [primaryKey];\n\n // Create callbacks for view option changes\n const onColumnsRenderModeChanged = (\n cols: Record<string, ColumnRenderMode>,\n ) => {\n const newRenderModes = {\n ...(viewOptions?.columnsRenderMode ?? {}),\n ...cols,\n };\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n columnsRenderMode: newRenderModes,\n });\n }\n };\n\n const handlePinnedColumnsChanged = (pinnedCols: string[]) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n pinned_columns: pinnedCols,\n });\n }\n };\n\n // Build grid data using toValueDiffGridConfigured\n const gridData = toValueDiffGridConfigured(run.result, primaryKeys, {\n changedOnly,\n pinnedColumns,\n onPinnedColumnsChange: handlePinnedColumnsChanged,\n columnsRenderMode,\n onColumnsRenderModeChanged,\n displayMode,\n });\n\n // Empty state when no columns (no data at all)\n if (gridData.columns.length === 0) {\n return { isEmpty: true };\n }\n\n // Build warnings array\n const limit = run.result?.limit ?? 0;\n const warnings: string[] = [];\n if (limit > 0 && run.result?.more) {\n warnings.push(\n `Warning: Displayed results are limited to ${limit.toLocaleString()} records. To ensure complete data retrieval, consider applying a LIMIT or WHERE clause to constrain the result set.`,\n );\n }\n\n // Build toolbar with display mode switch and changed only checkbox\n const toolbar = (\n <>\n <DiffDisplayModeSwitch\n displayMode={displayMode}\n onDisplayModeChanged={(newDisplayMode) => {\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n display_mode: newDisplayMode,\n });\n }\n }}\n />\n <ChangedOnlyCheckbox\n changedOnly={viewOptions?.changed_only}\n onChange={() => {\n const newChangedOnly = !viewOptions?.changed_only;\n if (onViewOptionsChanged) {\n onViewOptionsChanged({\n ...viewOptions,\n changed_only: newChangedOnly,\n });\n }\n }}\n />\n </>\n );\n\n // Toolbar-in-empty-state pattern: when changed_only is true but no changed rows\n if (changedOnly && gridData.rows.length === 0) {\n return {\n isEmpty: true,\n emptyMessage: \"No change\",\n toolbar,\n warnings: warnings.length > 0 ? warnings : undefined,\n warningStyle: \"amber\",\n };\n }\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n warnings: warnings.length > 0 ? warnings : undefined,\n warningStyle: \"amber\",\n toolbar,\n defaultColumnOptions: {\n resizable: true,\n maxWidth: 800,\n minWidth: 35,\n },\n noRowsMessage: \"No mismatched rows\",\n };\n },\n}) as ForwardRefExoticComponent<\n ValueDiffDetailResultViewProps & RefAttributes<DataGridHandle>\n>;\n\n// Re-export the view options type for convenience\nexport type { ValueDiffDetailViewOptions };\n","\"use client\";\n\n/**\n * @file ValueDiffForm.tsx\n * @description Form component for configuring value diff parameters.\n *\n * This component allows users to:\n * - View the model being compared\n * - Select primary keys for joining records\n * - Select specific columns to compare (or all columns)\n *\n * Uses the useModelColumns hook from @datarecce/ui/hooks to fetch column metadata.\n */\n\nimport Autocomplete from \"@mui/material/Autocomplete\";\nimport Box from \"@mui/material/Box\";\nimport Checkbox from \"@mui/material/Checkbox\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Stack from \"@mui/material/Stack\";\nimport TextField from \"@mui/material/TextField\";\nimport Typography from \"@mui/material/Typography\";\nimport { useEffect, useState } from \"react\";\nimport { useModelColumns } from \"../../hooks\";\nimport type { RunFormProps } from \"../run\";\n\nexport interface ValueDiffFormParams {\n model: string;\n primary_key?: string | (string | undefined)[];\n columns?: string[];\n}\n\ntype ValueDiffFormProp = RunFormProps<ValueDiffFormParams>;\n\nexport function ValueDiffForm({\n params,\n onParamsChanged,\n setIsReadyToExecute,\n}: ValueDiffFormProp) {\n const [allColumns, setAllColumns] = useState<boolean>(\n !params.columns || params.columns.length === 0,\n );\n\n const model = params.model;\n const primaryKey = params.primary_key;\n\n const {\n columns,\n primaryKey: nodePrimaryKey,\n isLoading,\n error,\n } = useModelColumns(params.model);\n\n useEffect(() => {\n if (!primaryKey && nodePrimaryKey) {\n onParamsChanged({\n ...params,\n primary_key: nodePrimaryKey,\n });\n }\n }, [primaryKey, nodePrimaryKey, params, onParamsChanged]);\n\n useEffect(() => {\n setIsReadyToExecute(!!(primaryKey && model));\n }, [primaryKey, model, setIsReadyToExecute]);\n\n const columnNames = columns.map((c) => c.name);\n\n // primaryKey can be an array or string, map to array\n const primaryKeys = Array.isArray(primaryKey)\n ? primaryKey\n : primaryKey\n ? [primaryKey]\n : undefined;\n\n if (isLoading) {\n return <Box>Loading...</Box>;\n }\n\n if (columnNames.length === 0 || error) {\n return (\n <Box>\n Error: Please provide the 'catalog.json' to list column\n candidates\n </Box>\n );\n }\n\n return (\n <Stack spacing={5} sx={{ m: \"8px 24px\", pb: \"200px\" }}>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Model\n </Typography>\n <TextField\n fullWidth\n size=\"small\"\n value={params.model}\n slotProps={{ input: { readOnly: true } }}\n />\n </Box>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Primary key\n </Typography>\n <Autocomplete\n multiple\n size=\"small\"\n disableCloseOnSelect\n options={columnNames}\n value={(primaryKeys ?? []).filter(\n (c): c is string => c !== undefined,\n )}\n onChange={(_, newValue) => {\n onParamsChanged({\n ...params,\n primary_key: newValue.length === 1 ? newValue[0] : newValue,\n });\n }}\n renderInput={(inputProps) => (\n <TextField\n {...inputProps}\n placeholder={\n (primaryKeys ?? []).length === 0 ? \"Select primary key\" : \"\"\n }\n className=\"no-track-pii-safe\"\n />\n )}\n />\n </Box>\n <Box>\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n Columns\n </Typography>\n <FormControlLabel\n control={\n <Checkbox\n checked={allColumns}\n onChange={(e) => {\n setAllColumns(e.target.checked);\n onParamsChanged({\n ...params,\n columns: undefined,\n });\n }}\n size=\"small\"\n />\n }\n label=\"All columns\"\n sx={{ mb: \"10px\" }}\n />\n {!allColumns && (\n <Autocomplete\n multiple\n size=\"small\"\n disableCloseOnSelect\n options={columnNames}\n value={params.columns ?? []}\n onChange={(_, newValue) => {\n onParamsChanged({\n ...params,\n columns: newValue.length === 0 ? undefined : newValue,\n });\n }}\n renderInput={(inputProps) => (\n <TextField\n {...inputProps}\n placeholder={\n (params.columns ?? []).length === 0 ? \"Select columns\" : \"\"\n }\n className=\"no-track-pii-safe\"\n />\n )}\n />\n )}\n </Box>\n </Stack>\n );\n}\n","\"use client\";\n\n/**\n * @file ValueDiffResultView.tsx\n * @description Framework-agnostic Value Diff summary result view for @datarecce/ui\n *\n * Displays column-level match statistics from a value_diff run in a data grid format.\n * Each row represents a column with its match count and percentage.\n *\n * Uses the createResultView factory pattern and can be used by both Recce OSS and Recce Cloud.\n */\n\nimport Box from \"@mui/material/Box\";\nimport type { ForwardRefExoticComponent, RefAttributes } from \"react\";\nimport {\n isValueDiffRun,\n type Run,\n type ValueDiffParams,\n type ValueDiffResult,\n} from \"../../api\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { createResultView } from \"../result/createResultView\";\nimport type { CreatedResultViewProps, ResultViewData } from \"../result/types\";\nimport { toValueDataGrid } from \"../ui/dataGrid\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Run type with value_diff result\n */\nexport type ValueDiffRun = Run & {\n type: \"value_diff\";\n result?: ValueDiffResult;\n params?: ValueDiffParams;\n};\n\n/**\n * Props for ValueDiffResultView component\n */\nexport interface ValueDiffResultViewProps\n extends CreatedResultViewProps<unknown> {\n run: ValueDiffRun | unknown;\n}\n\n// ============================================================================\n// Type Guard\n// ============================================================================\n\n/**\n * Type guard wrapper that accepts unknown and delegates to typed guard.\n */\nfunction isValueDiffRunGuard(run: unknown): run is ValueDiffRun {\n return isValueDiffRun(run as Run);\n}\n\n// ============================================================================\n// Header Component\n// ============================================================================\n\ninterface SummaryHeaderProps {\n params: ValueDiffParams;\n summary: ValueDiffResult[\"summary\"];\n}\n\nfunction SummaryHeader({ params, summary }: SummaryHeaderProps) {\n const common = summary.total - summary.added - summary.removed;\n\n return (\n <Box sx={{ px: \"16px\", pt: \"5px\", pb: \"5px\" }}>\n Model: {params.model}, {summary.total} total ({common} common,{\" \"}\n {summary.added} added, {summary.removed} removed)\n </Box>\n );\n}\n\n// ============================================================================\n// Transform Function\n// ============================================================================\n\nfunction transformValueDiffData(run: ValueDiffRun): ResultViewData | null {\n if (!run.result || !run.params) {\n return { renderNull: true };\n }\n\n const gridData = toValueDataGrid(run.result, { params: run.params });\n\n if (!gridData) {\n return { renderNull: true };\n }\n\n return {\n columns: gridData.columns,\n rows: gridData.rows,\n isEmpty: false,\n header: <SummaryHeader params={run.params} summary={run.result.summary} />,\n };\n}\n\n// ============================================================================\n// Factory-Created Component\n// ============================================================================\n\n/**\n * ValueDiffResultView component - displays value diff summary in a data grid.\n *\n * Features:\n * - Displays column-level match statistics\n * - Summary header with model name and row counts (total, common, added, removed)\n * - Highlights columns with match percentage < 100%\n *\n * @example\n * ```tsx\n * <ValueDiffResultView run={valueDiffRun} />\n * ```\n */\nexport const ValueDiffResultView = createResultView<\n ValueDiffRun,\n unknown,\n DataGridHandle\n>({\n displayName: \"ValueDiffResultView\",\n typeGuard: isValueDiffRunGuard,\n expectedRunType: \"value_diff\",\n screenshotWrapper: \"grid\",\n transformData: transformValueDiffData,\n}) as ForwardRefExoticComponent<\n ValueDiffResultViewProps & RefAttributes<DataGridHandle>\n>;\n","\"use client\";\n\n/**\n * @file run/registry.ts\n * @description Run type registry with icons and components.\n *\n * This module provides:\n * - `RegistryEntry` interface for typed registry entries\n * - `RunRegistry` interface for the full registry mapping\n * - `registry` const with all run type configurations\n * - `findByRunType()` helper for looking up registry entries\n *\n * All run type icons are from react-icons. Components available in\n * @datarecce/ui are included; others are undefined and can be extended.\n */\n\nimport { LuChartBarBig } from \"react-icons/lu\";\nimport { MdFormatListNumberedRtl, MdSchema } from \"react-icons/md\";\nimport {\n TbAlignBoxLeftStretch,\n TbBrandStackshare,\n TbChartHistogram,\n TbEyeEdit,\n TbEyeSearch,\n TbSql,\n} from \"react-icons/tb\";\nimport type { RunType } from \"../../api\";\nimport type { DataGridHandle } from \"../data/ScreenshotDataGrid\";\nimport { HistogramDiffForm } from \"../histogram/HistogramDiffForm\";\nimport { HistogramDiffResultView } from \"../histogram/HistogramResultView\";\nimport { ProfileDiffForm } from \"../profile/ProfileDiffForm\";\nimport {\n ProfileDiffResultView,\n ProfileResultView,\n} from \"../profile/ProfileResultView\";\nimport { QueryDiffResultView } from \"../query/QueryDiffResultView\";\nimport { QueryResultView } from \"../query/QueryResultView\";\nimport {\n RowCountDiffResultView,\n RowCountResultView,\n} from \"../rowcount/RowCountResultView\";\nimport { TopKDiffForm } from \"../top-k/TopKDiffForm\";\nimport { TopKDiffResultView } from \"../top-k/TopKDiffResultView\";\nimport { ValueDiffDetailResultView } from \"../valuediff/ValueDiffDetailResultView\";\nimport { ValueDiffForm } from \"../valuediff/ValueDiffForm\";\nimport { ValueDiffResultView } from \"../valuediff/ValueDiffResultView\";\nimport type {\n RefTypes,\n RegistryEntry,\n RunFormParamTypes,\n RunTypeConfig,\n ViewOptionTypes,\n} from \"./types\";\n\n// ============================================================================\n// Re-export types for consumers\n// ============================================================================\n\nexport type {\n IconComponent,\n PartialRunTypeRegistry,\n RefTypes,\n RegistryEntry,\n RunFormParamTypes,\n RunFormProps,\n RunResultViewProps,\n RunResultViewRef,\n RunTypeConfig,\n RunTypeRegistry,\n ViewOptionTypes,\n} from \"./types\";\n\n// ============================================================================\n// Run Registry Interface\n// ============================================================================\n\n/**\n * Interface for the run registry with specific component types for each run type.\n * This provides precise typing for each entry's ref type and view options.\n */\nexport interface RunRegistry {\n query: RegistryEntry<DataGridHandle>;\n query_base: RegistryEntry<DataGridHandle>;\n query_diff: RegistryEntry<DataGridHandle>;\n row_count: RegistryEntry<DataGridHandle>;\n row_count_diff: RegistryEntry<DataGridHandle>;\n profile: RegistryEntry<DataGridHandle>;\n profile_diff: RegistryEntry<DataGridHandle>;\n value_diff: RegistryEntry<DataGridHandle>;\n value_diff_detail: RegistryEntry<DataGridHandle>;\n top_k_diff: RegistryEntry<HTMLDivElement>;\n histogram_diff: RegistryEntry<HTMLDivElement>;\n lineage_diff: RegistryEntry<never>;\n schema_diff: RegistryEntry<never>;\n sandbox: RegistryEntry<never>;\n simple: RegistryEntry<never>;\n}\n\n// ============================================================================\n// Registry\n// ============================================================================\n\n/**\n * The run type registry with all icons and available components.\n *\n * Components in @datarecce/ui are included directly. Components still in\n * OSS (QueryResultView, ProfileDiffResultView, ValueDiffResultView, etc.)\n * are undefined here and should be injected by consumers.\n *\n * @example\n * ```ts\n * const entry = registry.query;\n * console.log(entry.title); // \"Query\"\n * console.log(entry.icon); // TbSql\n * ```\n */\nexport const registry: RunRegistry = {\n lineage_diff: {\n title: \"Lineage Diff\",\n icon: TbBrandStackshare,\n },\n schema_diff: {\n title: \"Schema Diff\",\n icon: MdSchema,\n },\n query: {\n title: \"Query\",\n icon: TbSql,\n RunResultView:\n QueryResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n query_base: {\n title: \"Query Base\",\n icon: TbSql,\n RunResultView:\n QueryResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n query_diff: {\n title: \"Query Diff\",\n icon: TbSql,\n RunResultView:\n QueryDiffResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n row_count: {\n title: \"Row Count\",\n icon: MdFormatListNumberedRtl,\n RunResultView:\n RowCountResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n row_count_diff: {\n title: \"Row Count Diff\",\n icon: MdFormatListNumberedRtl,\n RunResultView:\n RowCountDiffResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n },\n profile: {\n title: \"Profile\",\n icon: TbEyeSearch,\n RunResultView:\n ProfileResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n RunForm: ProfileDiffForm,\n },\n profile_diff: {\n title: \"Profile Diff\",\n icon: TbEyeSearch,\n RunResultView:\n ProfileDiffResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n RunForm: ProfileDiffForm,\n },\n value_diff: {\n title: \"Value Diff\",\n icon: TbAlignBoxLeftStretch,\n RunResultView:\n ValueDiffResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n RunForm: ValueDiffForm,\n },\n value_diff_detail: {\n title: \"Value Diff Detail\",\n icon: TbAlignBoxLeftStretch,\n RunResultView:\n ValueDiffDetailResultView as RegistryEntry<DataGridHandle>[\"RunResultView\"],\n RunForm: ValueDiffForm,\n },\n top_k_diff: {\n title: \"Top-K Diff\",\n icon: LuChartBarBig,\n RunResultView:\n TopKDiffResultView as RegistryEntry<HTMLDivElement>[\"RunResultView\"],\n RunForm: TopKDiffForm,\n },\n histogram_diff: {\n title: \"Histogram Diff\",\n icon: TbChartHistogram,\n RunResultView:\n HistogramDiffResultView as RegistryEntry<HTMLDivElement>[\"RunResultView\"],\n RunForm: HistogramDiffForm,\n },\n sandbox: {\n title: \"Sandbox\",\n icon: TbEyeEdit,\n },\n simple: {\n title: \"Simple\",\n icon: TbEyeEdit,\n },\n};\n\n// ============================================================================\n// Registry Lookup\n// ============================================================================\n\n/**\n * Find a run type configuration by run type.\n *\n * @param runType - The run type to look up\n * @returns The registry entry for the run type\n *\n * @example\n * ```ts\n * const entry = findByRunType(\"query\");\n * console.log(entry.title); // \"Query\"\n * console.log(entry.icon); // TbSql\n * ```\n */\nexport function findByRunType<T extends RunType>(runType: T): RunRegistry[T] {\n return registry[runType];\n}\n\n// ============================================================================\n// Legacy Exports (for backward compatibility)\n// ============================================================================\n\n/**\n * @deprecated Use `registry` directly instead\n */\nexport const defaultRunTypeConfig = registry;\n\n/**\n * Creates a run type registry with the provided configurations.\n * Merges with defaults to ensure all run types have entries.\n *\n * @param config - Partial or full registry configuration\n * @returns Complete registry with all run types\n *\n * @example\n * ```ts\n * const customRegistry = createRunTypeRegistry({\n * query: { ...registry.query, RunResultView: MyQueryResultView }\n * });\n * ```\n */\nexport function createRunTypeRegistry(\n config: Partial<Record<RunType, Partial<RunTypeConfig>>>,\n): RunRegistry {\n const result = { ...registry } as Record<RunType, RunTypeConfig>;\n\n for (const [type, overrides] of Object.entries(config) as [\n RunType,\n Partial<RunTypeConfig>,\n ][]) {\n if (overrides && type in result) {\n result[type] = {\n ...result[type],\n ...overrides,\n };\n }\n }\n\n return result as RunRegistry;\n}\n\n/**\n * Creates a bound lookup function for a specific registry.\n *\n * @param reg - The registry to bind\n * @returns A function that looks up run types in the bound registry\n *\n * @example\n * ```ts\n * const customRegistry = createRunTypeRegistry({ ... });\n * const findCustomRunType = createBoundFindByRunType(customRegistry);\n * const entry = findCustomRunType(\"query\");\n * ```\n */\nexport function createBoundFindByRunType(\n reg: RunRegistry,\n): <T extends RunType>(runType: T) => RunRegistry[T] {\n return <T extends RunType>(runType: T) => reg[runType];\n}\n","\"use client\";\n\n/**\n * @file run/RunListOss.tsx\n * @description OSS wrapper for RunList that injects OSS-specific dependencies.\n *\n * This component wraps the @datarecce/ui RunList with:\n * - Data fetching via React Query\n * - OSS-specific tracking\n * - Context integration (RecceActionContext, RecceInstanceContext)\n * - Navigation and check creation\n */\n\nimport IconButton from \"@mui/material/IconButton\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { useRouter } from \"next/navigation\";\nimport { useCallback, useMemo } from \"react\";\nimport { FaCheckCircle, FaRegCheckCircle } from \"react-icons/fa\";\nimport { PiX } from \"react-icons/pi\";\nimport { cacheKeys, createCheckByRun, listRuns, type Run } from \"../../api\";\nimport {\n useRecceActionContext,\n useRecceInstanceContext,\n useRouteConfig,\n} from \"../../contexts\";\nimport { useApiConfig } from \"../../hooks\";\nimport { trackHistoryAction } from \"../../lib/api/track\";\nimport { RunList as BaseRunList, type RunListItemData } from \"./RunList\";\nimport { findByRunType } from \"./registry\";\n\n/**\n * Transform API Run to RunListItemData for the UI component\n */\nfunction mapRunToListItem(run: Run): RunListItemData {\n return {\n id: run.run_id,\n name: run.name,\n type: run.type,\n // Default to \"Finished\" if status is not set (shouldn't happen in practice)\n status: run.status ?? \"Finished\",\n runAt: run.run_at,\n checkId: run.check_id,\n };\n}\n\n/**\n * RunListOss Component - OSS wrapper\n *\n * Provides the History panel with run list, integrating with OSS-specific\n * contexts, tracking, and navigation.\n *\n * @example\n * ```tsx\n * <RunListOss />\n * ```\n */\nexport function RunListOss() {\n const { closeHistory, showRunId, runId } = useRecceActionContext();\n const { featureToggles } = useRecceInstanceContext();\n const { apiClient } = useApiConfig();\n const router = useRouter();\n const queryClient = useQueryClient();\n const { basePath } = useRouteConfig();\n\n // Fetch all runs\n const { data: runs, isLoading } = useQuery({\n queryKey: cacheKeys.runs(),\n queryFn: async () => {\n // Cast from library Run[] to OSS Run[] for discriminated union support\n return (await listRuns(apiClient)) as Run[];\n },\n retry: false,\n });\n\n // Transform runs to list item data format\n const runListItems = useMemo(() => {\n return (runs ?? []).map(mapRunToListItem);\n }, [runs]);\n\n // Handle run selection with tracking\n const handleRunSelect = useCallback(\n (selectedRunId: string) => {\n trackHistoryAction({ name: \"click_run\" });\n showRunId(selectedRunId, false);\n },\n [showRunId],\n );\n\n // Handle add to checklist with tracking and navigation\n const handleAddToChecklist = useCallback(\n async (clickedRunId: string) => {\n trackHistoryAction({ name: \"add_to_checklist\" });\n const check = await createCheckByRun(clickedRunId, undefined, apiClient);\n await queryClient.invalidateQueries({ queryKey: cacheKeys.checks() });\n router.push(`${basePath}/checks/?id=${check.check_id}`);\n },\n [apiClient, queryClient, router.push, basePath],\n );\n\n // Handle go to check with tracking\n const handleGoToCheck = useCallback(\n (checkId: string) => {\n trackHistoryAction({ name: \"go_to_check\" });\n router.push(`${basePath}/checks/?id=${checkId}`);\n },\n [router.push, basePath],\n );\n\n // Handle close history with tracking\n const handleCloseHistory = useCallback(() => {\n trackHistoryAction({ name: \"hide\" });\n closeHistory();\n }, [closeHistory]);\n\n // Get icon for run type\n const getRunIcon = useCallback((runType: string) => {\n const registryEntry = findByRunType(\n runType as Parameters<typeof findByRunType>[0],\n );\n const IconComponent = registryEntry?.icon;\n return IconComponent ? <IconComponent /> : null;\n }, []);\n\n // Header action - close button\n const headerActions = (\n <IconButton aria-label=\"Close History\" onClick={handleCloseHistory}>\n <PiX />\n </IconButton>\n );\n\n return (\n <BaseRunList\n runs={runListItems}\n selectedId={runId}\n isLoading={isLoading}\n onRunSelect={handleRunSelect}\n onAddToChecklist={handleAddToChecklist}\n onGoToCheck={handleGoToCheck}\n getRunIcon={getRunIcon}\n hideAddToChecklist={featureToggles.disableUpdateChecklist}\n title=\"History\"\n headerActions={headerActions}\n emptyMessage=\"No runs\"\n loadingMessage=\"Loading...\"\n groupByDate={true}\n addToChecklistIcon={<FaRegCheckCircle />}\n goToCheckIcon={<FaCheckCircle color=\"green\" />}\n />\n );\n}\n","\"use client\";\n\n/**\n * @file run/RunModal.tsx\n * @description Modal dialog for configuring and executing runs with forms.\n *\n * This component provides a generic modal for run form interactions.\n * OSS-specific behavior (tracking, documentation URLs) is injected via props.\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Link from \"@mui/material/Link\";\nimport MuiPopover from \"@mui/material/Popover\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport { type ComponentType, ReactNode, useRef, useState } from \"react\";\nimport { IconBaseProps } from \"react-icons\";\nimport { IoClose } from \"react-icons/io5\";\nimport type { Run, RunType } from \"../../api\";\nimport type { RunFormProps } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Props for the RunModal component.\n *\n * @template PT - The type of form parameters used by the RunForm component\n */\nexport interface RunModalProps<PT = unknown> {\n /** Whether the modal is currently open */\n isOpen: boolean;\n\n /** Callback when the modal is closed (via X button, backdrop click, or escape key) */\n onClose: () => void;\n\n /** Callback when the execute button is clicked with valid parameters */\n onExecute: (type: RunType, params: PT) => void;\n\n /** The title displayed in the modal header */\n title: string;\n\n /** The run type being configured */\n type: RunType;\n\n /** Initial/default parameters for the form */\n params?: PT;\n\n /** The initial run to display (for edit scenarios) */\n initialRun?: Run;\n\n /** The form component to render for configuring run parameters */\n RunForm?: ComponentType<RunFormProps<PT>>;\n\n /**\n * Optional callback when the modal is cancelled (X button clicked without executing).\n * Use this for analytics/tracking of form cancellations.\n */\n onCancel?: () => void;\n\n /**\n * Optional callback when execute is clicked.\n * Use this for analytics/tracking of form submissions.\n */\n onExecuteClick?: () => void;\n\n /**\n * Optional documentation URL for the run type.\n * If provided, an info icon will be shown that links to the documentation.\n */\n documentationUrl?: string | null;\n\n /**\n * Optional icon component to display next to the documentation link.\n * Can be any component - will be wrapped by MUI Box with fontSize styling.\n * Compatible with react-icons, custom SVG components, or MUI icons.\n */\n InfoIcon?: ComponentType<IconBaseProps>;\n}\n\n// ============================================================================\n// Default Info Icon\n// ============================================================================\n\n/**\n * Default info icon component - a simple info circle.\n * OSS and Cloud consumers can override with their own icons.\n */\nconst DefaultInfoIcon = ({ size = 16 }: { size?: string | number }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n </svg>\n);\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * RunModal - A dialog for configuring and executing runs.\n *\n * This component renders a modal dialog with:\n * - A title with optional documentation link\n * - A form for configuring run parameters (optional)\n * - Execute and close actions\n *\n * The modal supports dependency injection for:\n * - Form component (`RunForm`) - for run-type-specific parameter forms\n * - Tracking callbacks (`onCancel`, `onExecuteClick`) - for analytics\n * - Documentation URL (`documentationUrl`) - for help links\n * - Info icon (`InfoIcon`) - for customizing the documentation link icon\n *\n * @example\n * ```tsx\n * <RunModal\n * isOpen={isOpen}\n * onClose={handleClose}\n * onExecute={handleExecute}\n * title=\"Profile Diff\"\n * type=\"profile_diff\"\n * params={{ model: \"my_model\" }}\n * RunForm={ProfileDiffForm}\n * documentationUrl=\"https://docs.reccehq.com/features/lineage/#profile-diff\"\n * />\n * ```\n */\nexport function RunModal<PT = unknown>({\n isOpen,\n onClose,\n onExecute,\n type,\n title,\n params: defaultParams,\n RunForm,\n onCancel,\n onExecuteClick,\n documentationUrl,\n InfoIcon = DefaultInfoIcon,\n}: RunModalProps<PT>) {\n const [params, setParams] = useState<Partial<PT>>(\n (defaultParams ?? {}) as Partial<PT>,\n );\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);\n const [isReadyToExecute, setIsReadyToExecute] = useState(false);\n const executeClicked = useRef(false);\n\n const handleClose = () => {\n if (!executeClicked.current) {\n // Track cancellation if callback provided\n onCancel?.();\n }\n executeClicked.current = false; // Reset for next open\n onClose();\n };\n\n const handleExecuteClick = () => {\n executeClicked.current = true;\n // Track execute click if callback provided\n onExecuteClick?.();\n onExecute(type, params as PT);\n };\n\n return (\n <MuiDialog\n open={isOpen}\n onClose={handleClose}\n maxWidth=\"sm\"\n fullWidth\n scroll=\"paper\"\n slotProps={{\n paper: { sx: { height: \"75%\", minHeight: \"400px\" } },\n }}\n >\n <DialogTitle sx={{ display: \"flex\", alignItems: \"center\" }}>\n {title}{\" \"}\n {documentationUrl && (\n <>\n <IconButton\n size=\"small\"\n aria-label=\"Click this button to learn more about the SQL behind\"\n onMouseEnter={(e) => setAnchorEl(e.currentTarget)}\n onMouseLeave={() => setAnchorEl(null)}\n onClick={() => window.open(documentationUrl, \"_blank\")}\n >\n <Box component={InfoIcon} sx={{ fontSize: \"16px\" }} />\n </IconButton>\n <MuiPopover\n open={Boolean(anchorEl)}\n anchorEl={anchorEl}\n onClose={() => setAnchorEl(null)}\n anchorOrigin={{\n vertical: \"bottom\",\n horizontal: \"right\",\n }}\n transformOrigin={{\n vertical: \"top\",\n horizontal: \"right\",\n }}\n disableRestoreFocus\n sx={{ pointerEvents: \"none\" }}\n slotProps={{\n paper: {\n sx: { bgcolor: \"black\", color: \"white\", p: 1 },\n },\n }}\n >\n <Typography sx={{ fontSize: \"0.875rem\" }}>\n Click{\" \"}\n <Link\n href={documentationUrl}\n target=\"_blank\"\n sx={{\n textDecoration: \"underline\",\n color: \"white\",\n \"&:hover\": { color: \"iochmara.300\" },\n }}\n >\n here\n </Link>{\" \"}\n to learn more about the SQL behind\n </Typography>\n </MuiPopover>\n </>\n )}\n </DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={handleClose}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n <DialogContent\n sx={{\n p: 0,\n overflow: \"auto\",\n borderTop: \"1px solid\",\n borderBottom: \"1px solid\",\n borderColor: \"divider\",\n }}\n >\n <Box sx={{ contain: \"layout\" }}>\n {RunForm && (\n <RunForm\n params={params}\n onParamsChanged={setParams}\n setIsReadyToExecute={setIsReadyToExecute}\n />\n )}\n </Box>\n </DialogContent>\n <DialogActions>\n <Stack direction=\"row\" spacing=\"10px\">\n <Button\n disabled={!isReadyToExecute}\n color=\"iochmara\"\n variant=\"contained\"\n onClick={handleExecuteClick}\n >\n Execute\n </Button>\n </Stack>\n </DialogActions>\n </MuiDialog>\n );\n}\n","/**\n * @file run/RunModalOss.tsx\n * @description OSS wrapper for RunModal from @datarecce/ui.\n *\n * This wrapper injects OSS-specific behavior:\n * - Tracking callbacks for analytics (Amplitude)\n * - Documentation URL mapping for run types\n */\n\nimport type { ComponentType } from \"react\";\nimport { PiInfo } from \"react-icons/pi\";\nimport type { Run, RunType } from \"../../api\";\nimport {\n EXPLORE_FORM_EVENT,\n isExploreAction,\n trackExploreActionForm,\n} from \"../../lib/api/track\";\nimport type { RunModalProps as UIRunModalProps } from \"./RunModal\";\nimport { RunModal as UIRunModal } from \"./RunModal\";\nimport type { RunFormParamTypes, RunFormProps } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * OSS-specific props for RunModal.\n * Extends the base props with OSS form parameter types.\n */\nexport interface RunModalProps {\n /** Whether the modal is currently open */\n isOpen: boolean;\n\n /** Callback when the modal is closed */\n onClose: () => void;\n\n /** Callback when the execute button is clicked */\n onExecute: (type: RunType, params: RunFormParamTypes) => void;\n\n /** The title displayed in the modal header */\n title: string;\n\n /** The run type being configured */\n type: RunType;\n\n /** Initial/default parameters for the form */\n params?: RunFormParamTypes;\n\n /** The initial run to display (for edit scenarios) */\n initialRun?: Run;\n\n /** The form component to render for configuring run parameters */\n RunForm?: ComponentType<RunFormProps<RunFormParamTypes>>;\n}\n\n// ============================================================================\n// Documentation URL Mapping\n// ============================================================================\n\n/**\n * Maps run types to their documentation URLs.\n * Returns null for run types without documentation.\n */\nconst getDocumentationUrl = (type: RunType): string | null => {\n const urlMap: Record<string, string> = {\n value_diff: \"https://docs.reccehq.com/features/lineage/#value-diff\",\n profile_diff: \"https://docs.reccehq.com/features/lineage/#profile-diff\",\n histogram_diff: \"https://docs.reccehq.com/features/lineage/#histogram-diff\",\n top_k_diff: \"https://docs.reccehq.com/features/lineage/#top-k-diff\",\n };\n return urlMap[type] || null;\n};\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * OSS RunModal - Wraps @datarecce/ui's RunModal with OSS-specific behavior.\n *\n * This wrapper:\n * - Injects tracking callbacks for form cancellation and execution\n * - Provides documentation URLs based on run type\n * - Uses the OSS IconInfo component for the documentation link\n *\n * @example\n * ```tsx\n * <RunModal\n * isOpen={isOpen}\n * onClose={handleClose}\n * onExecute={handleExecute}\n * title=\"Profile Diff\"\n * type=\"profile_diff\"\n * params={{ model: \"my_model\" }}\n * RunForm={ProfileDiffForm}\n * />\n * ```\n */\nexport function RunModalOss({\n isOpen,\n onClose,\n onExecute,\n type,\n title,\n params,\n initialRun,\n RunForm,\n}: RunModalProps) {\n // Track form cancellation for explore actions\n const handleCancel = () => {\n if (isExploreAction(type)) {\n trackExploreActionForm({\n action: type,\n event: EXPLORE_FORM_EVENT.CANCEL,\n });\n }\n };\n\n // Track form execution for explore actions\n const handleExecuteClick = () => {\n if (isExploreAction(type)) {\n trackExploreActionForm({\n action: type,\n event: EXPLORE_FORM_EVENT.EXECUTE,\n });\n }\n };\n\n return (\n <UIRunModal<RunFormParamTypes>\n isOpen={isOpen}\n onClose={onClose}\n onExecute={onExecute}\n type={type}\n title={title}\n params={params}\n initialRun={initialRun}\n RunForm={RunForm as UIRunModalProps<RunFormParamTypes>[\"RunForm\"]}\n onCancel={handleCancel}\n onExecuteClick={handleExecuteClick}\n documentationUrl={getDocumentationUrl(type)}\n InfoIcon={PiInfo}\n />\n );\n}\n","\"use client\";\n\n/**\n * @file run/RunView.tsx\n * @description Generic run view component for displaying run execution state and results.\n *\n * This component provides:\n * - Loading state with progress indicator\n * - Error state display\n * - Run result rendering via RunResultView component or children render prop\n * - Dependency injection for error boundaries (OSS uses Sentry, others can use custom)\n *\n * @example Basic usage with RunResultView\n * ```tsx\n * import { RunView } from \"@datarecce/ui/components/run\";\n * import { QueryResultView } from \"./QueryResultView\";\n *\n * function MyComponent() {\n * return (\n * <RunView\n * run={run}\n * isRunning={isRunning}\n * RunResultView={QueryResultView}\n * onCancel={handleCancel}\n * />\n * );\n * }\n * ```\n *\n * @example With children render prop\n * ```tsx\n * <RunView run={run} isRunning={isRunning}>\n * {({ run, viewOptions, onViewOptionsChanged }) => (\n * <CustomResultView run={run} viewOptions={viewOptions} />\n * )}\n * </RunView>\n * ```\n *\n * @example With error boundary injection (OSS pattern)\n * ```tsx\n * import { ErrorBoundary } from \"@datarecce/ui/components/errorboundary\";\n * import ResultErrorFallback from \"@datarecce/ui/lib/result/ResultErrorFallback\";\n *\n * <RunView\n * run={run}\n * ErrorBoundary={ErrorBoundary}\n * errorBoundaryFallback={ResultErrorFallback}\n * />\n * ```\n */\n\nimport MuiAlert from \"@mui/material/Alert\";\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport CircularProgress from \"@mui/material/CircularProgress\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport type {\n ComponentType,\n ForwardRefExoticComponent,\n ReactNode,\n Ref,\n RefAttributes,\n} from \"react\";\nimport { forwardRef } from \"react\";\n\nimport type { Run } from \"../../api\";\nimport { useIsDark } from \"../../hooks\";\nimport type { RunResultViewProps } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * API error shape for extracting error messages from axios responses.\n */\ninterface ApiError {\n response?: {\n data?: {\n detail?: string;\n };\n };\n}\n\n/**\n * Props for the error boundary wrapper component.\n * Compatible with Sentry ErrorBoundary and custom implementations.\n */\nexport interface ErrorBoundaryWrapperProps {\n /** The content to wrap with error boundary */\n children: ReactNode;\n /** Fallback to display when an error occurs */\n // biome-ignore lint/suspicious/noExplicitAny: Fallback type varies by implementation (Sentry FallbackRender, React element, etc.)\n fallback?: any;\n}\n\n/**\n * Props for the RunView component.\n *\n * Uses permissive types to support various run result view components.\n * Consumers can pass typed RunResultView components and the types will\n * be inferred correctly at the call site.\n */\nexport interface RunViewProps {\n /** Whether a run is currently executing */\n isRunning?: boolean;\n\n /** The run object containing execution state and results */\n run?: Run;\n\n /** Error that occurred during run execution */\n error?: Error | null;\n\n /** Progress information for the current run */\n progress?: Run[\"progress\"];\n\n /** Whether the run is being aborted */\n isAborting?: boolean;\n\n /**\n * Whether this is a check detail view.\n * @deprecated This prop may be removed in future versions.\n */\n isCheckDetail?: boolean;\n\n /** Callback when user cancels the run */\n onCancel?: () => void;\n\n /** Callback to execute/re-execute the run */\n onExecuteRun?: () => void;\n\n /** Current view options for result display */\n // biome-ignore lint/suspicious/noExplicitAny: View options type varies by run type\n viewOptions?: any;\n\n /** Callback when view options change */\n // biome-ignore lint/suspicious/noExplicitAny: View options type varies by run type\n onViewOptionsChanged?: (viewOptions: any) => void;\n\n /**\n * Component to render run results.\n * Either RunResultView or children is required.\n */\n RunResultView?: ForwardRefExoticComponent<\n // biome-ignore lint/suspicious/noExplicitAny: RunResultView types vary by run type\n RunResultViewProps<any> & RefAttributes<any>\n >;\n\n /**\n * Render prop for custom result rendering.\n * Either RunResultView or children is required.\n */\n // biome-ignore lint/suspicious/noExplicitAny: Render prop view options vary by run type\n children?: (params: RunResultViewProps<any>) => ReactNode;\n\n // ============================================================================\n // Dependency Injection Props\n // ============================================================================\n\n /**\n * Error boundary component to wrap the result view.\n * If not provided, results are rendered without error boundary.\n *\n * @example Using Sentry ErrorBoundary\n * ```tsx\n * import { ErrorBoundary } from \"@sentry/react\";\n * <RunView ErrorBoundary={ErrorBoundary} />\n * ```\n */\n ErrorBoundary?: ComponentType<ErrorBoundaryWrapperProps>;\n\n /**\n * Fallback element/render function for the error boundary.\n * Type depends on the ErrorBoundary implementation used.\n *\n * @example Using Sentry FallbackRender\n * ```tsx\n * import ResultErrorFallback from \"@datarecce/ui/lib/result/ResultErrorFallback\";\n * <RunView errorBoundaryFallback={ResultErrorFallback} />\n * ```\n */\n // biome-ignore lint/suspicious/noExplicitAny: Fallback type varies by error boundary implementation\n errorBoundaryFallback?: any;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Generic run view component that displays run execution state and results.\n *\n * States:\n * 1. **Error state**: Shows error message from API response or run.error\n * 2. **Running state**: Shows loading spinner with progress and cancel button\n * 3. **Loading state**: Shows spinner when run is undefined\n * 4. **Result state**: Renders RunResultView or children with run results\n *\n * @remarks\n * The component uses forwardRef to pass refs to the RunResultView component,\n * enabling features like screenshot capture for data grids.\n *\n * @example\n * ```tsx\n * const ref = useRef<DataGridHandle>(null);\n *\n * <RunView\n * ref={ref}\n * run={run}\n * RunResultView={QueryResultView}\n * viewOptions={viewOptions}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const RunView = forwardRef<unknown, RunViewProps>(function RunView(\n {\n isRunning,\n isAborting,\n progress,\n error,\n run,\n onCancel,\n viewOptions,\n onViewOptionsChanged,\n RunResultView,\n children,\n ErrorBoundary,\n errorBoundaryFallback,\n },\n ref,\n) {\n const isDark = useIsDark();\n const errorMessage =\n (error as ApiError | undefined)?.response?.data?.detail ?? run?.error;\n\n // ============================================================================\n // Error State\n // ============================================================================\n if (errorMessage) {\n return (\n <MuiAlert\n severity=\"error\"\n sx={\n isDark\n ? {\n bgcolor: \"error.dark\",\n color: \"common.white\",\n \"& .MuiAlert-icon\": {\n color: \"common.white\",\n },\n }\n : undefined\n }\n >\n Error: <span className=\"no-track-pii-safe\">{errorMessage}</span>\n </MuiAlert>\n );\n }\n\n // ============================================================================\n // Running State\n // ============================================================================\n if (isRunning || run?.status === \"Running\") {\n let loadingMessage = \"Loading...\";\n if (progress?.message) {\n loadingMessage = progress.message;\n } else if (run?.progress?.message) {\n loadingMessage = run.progress.message;\n }\n\n const progressValue =\n progress?.percentage != null ? progress.percentage * 100 : undefined;\n\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n p: \"1rem\",\n height: \"100%\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n }}\n >\n <Stack spacing={2} alignItems=\"center\">\n <Stack direction=\"row\" alignItems=\"center\" spacing={1}>\n {progressValue == null ? (\n <CircularProgress size={32} />\n ) : (\n <Box sx={{ position: \"relative\", display: \"inline-flex\" }}>\n <CircularProgress\n variant=\"determinate\"\n value={progressValue}\n size={32}\n />\n <Box\n sx={{\n top: 0,\n left: 0,\n bottom: 0,\n right: 0,\n position: \"absolute\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Typography\n variant=\"caption\"\n component=\"div\"\n sx={{ fontSize: \"0.6rem\" }}\n >\n {`${Math.round(progressValue)}%`}\n </Typography>\n </Box>\n </Box>\n )}\n\n {isAborting ? (\n <Typography>Aborting...</Typography>\n ) : (\n <Typography className=\"no-track-pii-safe\">\n {loadingMessage}\n </Typography>\n )}\n </Stack>\n {!isAborting && (\n <Button variant=\"contained\" onClick={onCancel} size=\"small\">\n Cancel\n </Button>\n )}\n </Stack>\n </Box>\n );\n }\n\n // ============================================================================\n // Loading State (No Run Yet)\n // ============================================================================\n if (!run) {\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n height: \"100%\",\n }}\n >\n <CircularProgress size={32} />\n </Box>\n );\n }\n\n // ============================================================================\n // Validation\n // ============================================================================\n if (children && RunResultView) {\n throw new Error(\n \"RunView requires either a children or a RunResultView prop, but not both.\",\n );\n }\n if (!children && !RunResultView) {\n throw new Error(\n \"RunView requires at least one of children or RunResultView prop.\",\n );\n }\n\n // ============================================================================\n // Result State\n // ============================================================================\n\n /**\n * Renders the result content, optionally wrapped with an error boundary.\n */\n const renderResultContent = () => {\n const resultView =\n RunResultView && (run.error ?? run.result) ? (\n <RunResultView\n ref={ref}\n run={run}\n viewOptions={viewOptions}\n onViewOptionsChanged={onViewOptionsChanged}\n />\n ) : null;\n\n const childContent = children?.({ run, viewOptions, onViewOptionsChanged });\n\n // If ErrorBoundary is provided, wrap the content\n if (ErrorBoundary && resultView) {\n return (\n <>\n <ErrorBoundary fallback={errorBoundaryFallback}>\n {resultView}\n </ErrorBoundary>\n {childContent}\n </>\n );\n }\n\n // Otherwise render without error boundary\n return (\n <>\n {resultView}\n {childContent}\n </>\n );\n };\n\n return (\n <Box\n sx={{\n height: \"100%\",\n contain: \"layout\",\n overflow: \"auto\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n }}\n className=\"no-track-pii-safe\"\n >\n {renderResultContent()}\n </Box>\n );\n});\n\n// Set display name for debugging\nRunView.displayName = \"RunView\";\n","\"use client\";\n\n/**\n * @file run/RunResultPane.tsx\n * @description Reusable run result pane component with dependency injection support.\n *\n * This component provides the core UI for displaying run results including:\n * - Tab navigation (Result, Params, Query)\n * - Run status display\n * - Export/Share menus (injectable)\n * - Add to checklist functionality (injectable)\n *\n * OSS-specific behaviors are injected via props to maintain platform independence.\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Divider from \"@mui/material/Divider\";\nimport IconButton from \"@mui/material/IconButton\";\nimport ListItemIcon from \"@mui/material/ListItemIcon\";\nimport ListItemText from \"@mui/material/ListItemText\";\nimport Menu from \"@mui/material/Menu\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Stack from \"@mui/material/Stack\";\nimport Tab from \"@mui/material/Tab\";\nimport Tabs from \"@mui/material/Tabs\";\nimport Typography from \"@mui/material/Typography\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport {\n type ComponentType,\n type ForwardRefExoticComponent,\n type MouseEvent,\n memo,\n type ReactNode,\n type Ref,\n type RefAttributes,\n useCallback,\n useState,\n} from \"react\";\nimport { IoClose } from \"react-icons/io5\";\nimport {\n PiCaretDown,\n PiCheck,\n PiClipboardText,\n PiDownloadSimple,\n PiImage,\n PiRepeat,\n PiTable,\n} from \"react-icons/pi\";\nimport { TbCloudUpload } from \"react-icons/tb\";\nimport YAML from \"yaml\";\nimport type { Run, RunParamTypes } from \"../../api\";\nimport { useIsDark } from \"../../hooks/useIsDark\";\nimport { CodeEditor } from \"../../primitives\";\nimport { RunView } from \"./RunView\";\nimport type { RunResultViewProps, RunResultViewRef } from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Tab values for the run result pane\n */\nexport type RunResultPaneTabValue = \"result\" | \"params\" | \"query\";\n\n/**\n * Props for CSV export functionality\n */\nexport interface CSVExportProps {\n /** Whether CSV export is available */\n canExportCSV: boolean;\n /** Copy data as CSV to clipboard */\n copyAsCSV: () => Promise<void>;\n /** Copy data as TSV to clipboard (pastes into spreadsheets) */\n copyAsTSV?: () => Promise<void>;\n /** Download data as CSV file */\n downloadAsCSV: () => void;\n /** Download data as TSV file */\n downloadAsTSV?: () => void;\n /** Download data as Excel file */\n downloadAsExcel?: () => void;\n}\n\n/**\n * Props for the export menu component\n */\nexport interface RunResultExportMenuProps {\n /** The current run */\n run?: Run;\n /** Whether export is disabled */\n disableExport: boolean;\n /** Handler for copy as image */\n onCopyAsImage: () => Promise<void>;\n /** Handler for mouse enter (for highlight effect) */\n onMouseEnter?: () => void;\n /** Handler for mouse leave (for highlight effect) */\n onMouseLeave?: () => void;\n /** CSV export functionality */\n csvExport?: CSVExportProps;\n}\n\n/**\n * Props for the share menu component\n */\nexport interface RunResultShareMenuProps extends RunResultExportMenuProps {\n /** Whether user is authenticated */\n authed?: boolean;\n /** Handler for share to cloud */\n onShareToCloud?: () => Promise<void>;\n /** Handler for showing auth modal when not authenticated */\n onShowAuthModal?: () => void;\n}\n\n/**\n * Props for the Add to Check button\n */\nexport interface AddToCheckButtonProps {\n /** The current run ID */\n runId?: string;\n /** The current run */\n run?: Run;\n /** Whether the button is disabled due to feature toggle */\n disableUpdateChecklist?: boolean;\n /** Whether there's an error */\n hasError?: boolean;\n /** Handler for navigating to existing check */\n onGoToCheck?: (checkId: string) => void;\n /** Handler for adding run to checklist */\n onAddToChecklist?: () => Promise<void>;\n}\n\n/**\n * Props for the single environment setup notification\n */\nexport interface SingleEnvironmentNotificationProps {\n /** The run type */\n runType?: string;\n /** Component to render the notification */\n NotificationComponent?: ComponentType<{\n runType?: string;\n onClose: () => void;\n }>;\n}\n\n/**\n * Props for the SQL editor components\n */\nexport interface SqlEditorProps {\n /** SQL query value */\n value: string;\n /** Base SQL query value (for diff views) */\n baseValue?: string;\n /** Whether the editor is read-only */\n readOnly?: boolean;\n}\n\n/**\n * Props for RunResultPane component.\n * Uses dependency injection for OSS-specific behavior.\n */\nexport interface RunResultPaneProps<VO = unknown, RefType = unknown> {\n // ============================================================================\n // Core Data\n // ============================================================================\n\n /** The run ID */\n runId?: string;\n\n /** The run object */\n run?: Run;\n\n /** Whether the run is currently executing */\n isRunning?: boolean;\n\n /** Error object if run failed */\n error?: Error | null;\n\n // ============================================================================\n // View Configuration\n // ============================================================================\n\n /** Current view options */\n viewOptions?: VO;\n\n /** Callback when view options change */\n onViewOptionsChanged?: (viewOptions: VO) => void;\n\n /** Whether this is a single environment (base not configured) */\n isSingleEnvironment?: boolean;\n\n // ============================================================================\n // Feature Toggles\n // ============================================================================\n\n /** Disable database query execution */\n disableDatabaseQuery?: boolean;\n\n /** Disable share functionality (show export menu instead) */\n disableShare?: boolean;\n\n /** Disable update checklist functionality */\n disableUpdateChecklist?: boolean;\n\n // ============================================================================\n // Event Handlers\n // ============================================================================\n\n /** Handler for closing the pane */\n onClose?: () => void;\n\n /** Handler for cancelling a running query */\n onCancel?: () => void;\n\n /** Handler for re-running the query */\n onRerun?: () => void;\n\n // ============================================================================\n // Export/Share Handlers (Dependency Injection)\n // ============================================================================\n\n /** Handler for copying as image */\n onCopyAsImage?: () => Promise<void>;\n\n /** Mouse enter handler for copy button highlight */\n onCopyMouseEnter?: () => void;\n\n /** Mouse leave handler for copy button highlight */\n onCopyMouseLeave?: () => void;\n\n /** CSV export functionality */\n csvExport?: CSVExportProps;\n\n /** Whether user is authenticated (for share menu) */\n authed?: boolean;\n\n /** Handler for share to cloud */\n onShareToCloud?: () => Promise<void>;\n\n /** Handler for showing auth modal */\n onShowAuthModal?: () => void;\n\n /** Optional tracking callback for copy to clipboard */\n onTrackCopyToClipboard?: (type: string, from: string) => void;\n\n // ============================================================================\n // Checklist Handlers (Dependency Injection)\n // ============================================================================\n\n /** Handler for navigating to existing check */\n onGoToCheck?: (checkId: string) => void;\n\n /** Handler for adding run to checklist */\n onAddToChecklist?: () => Promise<void>;\n\n // ============================================================================\n // Custom Components (Dependency Injection)\n // ============================================================================\n\n /** Custom notification component for single environment setup */\n SingleEnvironmentNotification?: ComponentType<{\n runType?: string;\n onClose: () => void;\n }>;\n\n /** Custom SQL editor component */\n SqlEditorComponent?: ComponentType<SqlEditorProps>;\n\n /** Custom dual SQL editor component (for query diff) */\n DualSqlEditorComponent?: ComponentType<SqlEditorProps>;\n\n /** Custom auth modal component */\n AuthModalComponent?: ComponentType<{\n open: boolean;\n onClose: () => void;\n }>;\n\n /** Result view component from registry */\n RunResultView?: ForwardRefExoticComponent<\n RunResultViewProps<VO> & RefAttributes<RefType>\n >;\n\n /** Ref for the result view (for screenshots) */\n resultViewRef?: Ref<RefType>;\n\n // ============================================================================\n // Children (Alternative to RunResultView)\n // ============================================================================\n\n /** Custom result renderer */\n children?: (props: {\n run: Run;\n viewOptions?: VO;\n onViewOptionsChanged?: (viewOptions: VO) => void;\n }) => ReactNode;\n}\n\n// ============================================================================\n// Internal Components\n// ============================================================================\n\n/**\n * Params view component - displays run parameters as YAML\n */\nconst ParamView = memo(\n ({ type, params }: { type: string; params: RunParamTypes }) => {\n const isDark = useIsDark();\n const yaml = YAML.stringify({ type, params }, null, 2);\n return (\n <CodeEditor\n value={yaml}\n language=\"yaml\"\n readOnly={true}\n lineNumbers={false}\n wordWrap={true}\n fontSize={14}\n height=\"100%\"\n theme={isDark ? \"dark\" : \"light\"}\n className=\"no-track-pii-safe\"\n />\n );\n },\n);\nParamView.displayName = \"ParamView\";\n\n/**\n * Default export menu component\n */\nconst DefaultExportMenu = memo(\n ({\n disableExport,\n onCopyAsImage,\n onMouseEnter,\n onMouseLeave,\n csvExport,\n }: RunResultExportMenuProps) => {\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);\n const open = Boolean(anchorEl);\n\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(event.currentTarget);\n };\n\n const handleClose = () => {\n setAnchorEl(null);\n };\n\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"neutral\"\n onClick={handleClick}\n endIcon={<PiCaretDown />}\n sx={{ textTransform: \"none\" }}\n >\n Export\n </Button>\n <Menu anchorEl={anchorEl} open={open} onClose={handleClose}>\n <MenuItem\n onClick={async () => {\n await onCopyAsImage();\n handleClose();\n }}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n disabled={disableExport}\n >\n <ListItemIcon>\n <PiImage />\n </ListItemIcon>\n <ListItemText>Copy as Image</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={async () => {\n await csvExport?.copyAsTSV?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiClipboardText />\n </ListItemIcon>\n <ListItemText>Copy as Text</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={async () => {\n await csvExport?.copyAsCSV();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiTable />\n </ListItemIcon>\n <ListItemText>Copy as CSV</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsCSV();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as CSV</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsTSV?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as TSV</ListItemText>\n </MenuItem>\n {csvExport?.downloadAsExcel && (\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsExcel?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as Excel</ListItemText>\n </MenuItem>\n )}\n </Menu>\n </>\n );\n },\n);\nDefaultExportMenu.displayName = \"DefaultExportMenu\";\n\n/**\n * Default share menu component\n */\nconst DefaultShareMenu = memo(\n ({\n disableExport,\n onCopyAsImage,\n onMouseEnter,\n onMouseLeave,\n csvExport,\n authed,\n onShareToCloud,\n onShowAuthModal,\n }: RunResultShareMenuProps) => {\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);\n const open = Boolean(anchorEl);\n\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(event.currentTarget);\n };\n\n const handleClose = () => {\n setAnchorEl(null);\n };\n\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"neutral\"\n onClick={handleClick}\n endIcon={<PiCaretDown />}\n sx={{ textTransform: \"none\" }}\n >\n Share\n </Button>\n <Menu anchorEl={anchorEl} open={open} onClose={handleClose}>\n <MenuItem\n onClick={async () => {\n await onCopyAsImage();\n handleClose();\n }}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n disabled={disableExport}\n >\n <ListItemIcon>\n <PiImage />\n </ListItemIcon>\n <ListItemText>Copy as Image</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={async () => {\n await csvExport?.copyAsTSV?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiClipboardText />\n </ListItemIcon>\n <ListItemText>Copy as Text</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={async () => {\n await csvExport?.copyAsCSV();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiTable />\n </ListItemIcon>\n <ListItemText>Copy as CSV</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsCSV();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as CSV</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsTSV?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as TSV</ListItemText>\n </MenuItem>\n {csvExport?.downloadAsExcel && (\n <MenuItem\n onClick={() => {\n csvExport?.downloadAsExcel?.();\n handleClose();\n }}\n disabled={disableExport || !csvExport?.canExportCSV}\n >\n <ListItemIcon>\n <PiDownloadSimple />\n </ListItemIcon>\n <ListItemText>Download as Excel</ListItemText>\n </MenuItem>\n )}\n <Divider />\n {authed ? (\n <MenuItem\n onClick={async () => {\n await onShareToCloud?.();\n handleClose();\n }}\n >\n <ListItemIcon>\n <TbCloudUpload />\n </ListItemIcon>\n <ListItemText>Share to Cloud</ListItemText>\n </MenuItem>\n ) : (\n <MenuItem\n onClick={() => {\n onShowAuthModal?.();\n handleClose();\n }}\n >\n <ListItemIcon>\n <TbCloudUpload />\n </ListItemIcon>\n <ListItemText>Share</ListItemText>\n </MenuItem>\n )}\n </Menu>\n </>\n );\n },\n);\nDefaultShareMenu.displayName = \"DefaultShareMenu\";\n\n/**\n * Default Add to Check button component\n */\nconst DefaultAddToCheckButton = memo(\n ({\n runId,\n run,\n disableUpdateChecklist,\n hasError,\n onGoToCheck,\n onAddToChecklist,\n }: AddToCheckButtonProps) => {\n const checkId = run?.check_id;\n const disabled = !runId || !run?.result || hasError;\n\n if (disableUpdateChecklist) {\n return null;\n }\n\n if (checkId) {\n return (\n <Button\n disabled={disabled}\n size=\"small\"\n variant=\"contained\"\n onClick={() => onGoToCheck?.(checkId)}\n startIcon={<PiCheck />}\n sx={{ textTransform: \"none\" }}\n >\n Go to Check\n </Button>\n );\n }\n\n return (\n <Button\n disabled={disabled}\n size=\"small\"\n variant=\"contained\"\n onClick={onAddToChecklist}\n startIcon={<PiCheck />}\n sx={{ textTransform: \"none\" }}\n >\n Add to Checklist\n </Button>\n );\n },\n);\nDefaultAddToCheckButton.displayName = \"DefaultAddToCheckButton\";\n\n/**\n * Run status and date display component\n */\nconst RunStatusAndDateDisplay = memo(({ run }: { run: Run }) => {\n const statusText =\n run.status || (run.result ? \"Finished\" : run.error ? \"Failed\" : \"unknown\");\n\n // Determine color based on status\n const getStatusColor = (status: string) => {\n switch (status.toLowerCase()) {\n case \"finished\":\n return \"success.main\";\n case \"failed\":\n return \"error.main\";\n case \"running\":\n return \"primary.main\";\n case \"cancelled\":\n default:\n return \"text.secondary\";\n }\n };\n\n const relativeTime = run.run_at\n ? formatDistanceToNow(new Date(run.run_at), { addSuffix: true })\n : \"Unknown time\";\n\n return (\n <Typography variant=\"body2\" sx={{ color: \"text.secondary\" }}>\n <Box\n component=\"span\"\n sx={{ color: getStatusColor(statusText) }}\n fontWeight={600}\n >\n {statusText}\n </Box>\n {\"・\"}\n {relativeTime}\n </Typography>\n );\n});\nRunStatusAndDateDisplay.displayName = \"RunStatusAndDateDisplay\";\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\n/**\n * RunResultPane Component\n *\n * A reusable component for displaying run results with tabs for Result, Params, and Query.\n * Uses dependency injection for OSS-specific behaviors like tracking, sharing, and checklist.\n *\n * @example Basic usage with run data\n * ```tsx\n * import { RunResultPane } from '@datarecce/ui/components/run';\n *\n * function MyRunView({ run }) {\n * return (\n * <RunResultPane\n * run={run}\n * runId={run.run_id}\n * onClose={() => handleClose()}\n * onRerun={() => handleRerun()}\n * RunResultView={MyResultView}\n * />\n * );\n * }\n * ```\n *\n * @example With OSS-specific injections\n * ```tsx\n * <RunResultPane\n * run={run}\n * runId={run.run_id}\n * onCopyAsImage={handleCopyAsImage}\n * csvExport={{ canExportCSV: true, copyAsCSV, downloadAsCSV }}\n * onShareToCloud={handleShare}\n * onTrackCopyToClipboard={(type, from) => trackCopyToClipboard({ type, from })}\n * onAddToChecklist={handleAddToChecklist}\n * authed={isAuthenticated}\n * RunResultView={QueryResultView}\n * />\n * ```\n */\nfunction RunResultPaneComponent<VO = unknown, RefType = unknown>({\n // Core data\n runId,\n run,\n isRunning,\n error,\n\n // View configuration\n viewOptions,\n onViewOptionsChanged,\n isSingleEnvironment,\n\n // Feature toggles\n disableDatabaseQuery,\n disableShare,\n disableUpdateChecklist,\n\n // Event handlers\n onClose,\n onCancel: _onCancel,\n onRerun,\n\n // Export/Share handlers\n onCopyAsImage,\n onCopyMouseEnter,\n onCopyMouseLeave,\n csvExport,\n authed,\n onShareToCloud,\n onShowAuthModal,\n onTrackCopyToClipboard,\n\n // Checklist handlers\n onGoToCheck,\n onAddToChecklist,\n\n // Custom components\n SingleEnvironmentNotification,\n SqlEditorComponent,\n DualSqlEditorComponent,\n AuthModalComponent,\n RunResultView,\n resultViewRef,\n\n // Children\n children,\n}: RunResultPaneProps<VO, RefType>) {\n const isDark = useIsDark();\n const [tabValue, setTabValue] = useState<RunResultPaneTabValue>(\"result\");\n const [showSingleEnvNotification, setShowSingleEnvNotification] =\n useState(true);\n const [showAuthModal, setShowAuthModal] = useState(false);\n\n const isQuery =\n run?.type === \"query\" ||\n run?.type === \"query_diff\" ||\n run?.type === \"query_base\";\n\n const disableCopyToClipboard =\n !runId || !run?.result || !!error || tabValue !== \"result\";\n\n const handleCopyAsImage = useCallback(async () => {\n await onCopyAsImage?.();\n if (onTrackCopyToClipboard) {\n onTrackCopyToClipboard(run?.type ?? \"unknown\", \"run\");\n }\n }, [onCopyAsImage, onTrackCopyToClipboard, run?.type]);\n\n const handleShowAuthModal = useCallback(() => {\n if (onShowAuthModal) {\n onShowAuthModal();\n } else {\n setShowAuthModal(true);\n }\n }, [onShowAuthModal]);\n\n // Determine if we should show query tab content\n const isQueryDiff = run?.type === \"query_diff\";\n const queryParams = run?.params as\n | { sql_template?: string; base_sql_template?: string }\n | undefined;\n\n return (\n <Box\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n height: \"100%\",\n bgcolor: isDark ? \"grey.900\" : \"grey.50\",\n }}\n >\n {/* Single environment notification */}\n {isSingleEnvironment &&\n showSingleEnvNotification &&\n SingleEnvironmentNotification && (\n <SingleEnvironmentNotification\n runType={run?.type}\n onClose={() => setShowSingleEnvNotification(false)}\n />\n )}\n\n {/* Header with tabs and actions */}\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n borderBottom: 1,\n borderColor: \"divider\",\n mb: \"1px\",\n }}\n >\n <Tabs\n value={tabValue}\n onChange={(_, newValue) =>\n setTabValue(newValue as RunResultPaneTabValue)\n }\n >\n <Tab label=\"Result\" value=\"result\" />\n <Tab label=\"Params\" value=\"params\" />\n {isQuery && <Tab label=\"Query\" value=\"query\" />}\n </Tabs>\n <Box sx={{ flexGrow: 1 }} />\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ overflow: \"hidden\", pr: 1 }}\n alignItems=\"center\"\n >\n {run && <RunStatusAndDateDisplay run={run} />}\n <Button\n variant=\"outlined\"\n color=\"neutral\"\n disabled={!runId || isRunning || disableDatabaseQuery}\n size=\"small\"\n onClick={onRerun}\n startIcon={<PiRepeat />}\n sx={{ textTransform: \"none\" }}\n >\n Rerun\n </Button>\n\n {/* Export or Share menu */}\n {disableShare ? (\n <DefaultExportMenu\n run={run}\n disableExport={disableCopyToClipboard}\n onCopyAsImage={handleCopyAsImage}\n onMouseEnter={onCopyMouseEnter}\n onMouseLeave={onCopyMouseLeave}\n csvExport={csvExport}\n />\n ) : (\n <DefaultShareMenu\n run={run}\n disableExport={disableCopyToClipboard}\n onCopyAsImage={handleCopyAsImage}\n onMouseEnter={onCopyMouseEnter}\n onMouseLeave={onCopyMouseLeave}\n csvExport={csvExport}\n authed={authed}\n onShareToCloud={onShareToCloud}\n onShowAuthModal={handleShowAuthModal}\n />\n )}\n\n {/* Add to Check button */}\n <DefaultAddToCheckButton\n runId={runId}\n run={run}\n disableUpdateChecklist={disableUpdateChecklist}\n hasError={!!error}\n onGoToCheck={onGoToCheck}\n onAddToChecklist={onAddToChecklist}\n />\n\n {/* Close button */}\n <IconButton size=\"small\" onClick={onClose}>\n <IoClose />\n </IconButton>\n </Stack>\n </Box>\n\n {/* Tab content */}\n {tabValue === \"result\" && (RunResultView || children) && (\n <RunView\n ref={resultViewRef}\n isRunning={isRunning}\n error={error}\n run={run}\n onCancel={_onCancel}\n viewOptions={viewOptions}\n onViewOptionsChanged={onViewOptionsChanged}\n RunResultView={RunResultView}\n >\n {children}\n </RunView>\n )}\n\n {tabValue === \"params\" && run && (\n <ParamView type={run.type} params={run.params} />\n )}\n\n {tabValue === \"query\" && run && isQuery && queryParams?.sql_template && (\n <>\n {isQueryDiff && DualSqlEditorComponent ? (\n <DualSqlEditorComponent\n value={queryParams.sql_template}\n baseValue={queryParams.base_sql_template}\n readOnly={true}\n />\n ) : SqlEditorComponent ? (\n <SqlEditorComponent\n value={queryParams.sql_template}\n readOnly={true}\n />\n ) : (\n <CodeEditor\n value={queryParams.sql_template}\n language=\"sql\"\n readOnly={true}\n theme=\"dark\"\n height=\"100%\"\n />\n )}\n </>\n )}\n\n {/* Auth modal */}\n {AuthModalComponent && showAuthModal && (\n <AuthModalComponent\n open={showAuthModal}\n onClose={() => setShowAuthModal(false)}\n />\n )}\n </Box>\n );\n}\n\nexport const RunResultPane = memo(RunResultPaneComponent) as <\n VO = unknown,\n RefType = unknown,\n>(\n props: RunResultPaneProps<VO, RefType>,\n) => ReturnType<typeof RunResultPaneComponent>;\n\n// Add display name for debugging\n(RunResultPane as { displayName?: string }).displayName = \"RunResultPane\";\n","import Box from \"@mui/material/Box\";\nimport IconButton from \"@mui/material/IconButton\";\nimport MuiLink from \"@mui/material/Link\";\nimport { PropsWithChildren } from \"react\";\nimport { FiInfo } from \"react-icons/fi\";\nimport { IoClose } from \"react-icons/io5\";\nimport { LuExternalLink } from \"react-icons/lu\";\n\nexport const RecceNotification = (\n props: PropsWithChildren<{\n onClose: () => void;\n align?: string;\n }>,\n) => {\n return (\n <Box\n sx={{\n display: \"flex\",\n flex: 1,\n minHeight: \"48px\",\n m: \"4px\",\n px: \"16px\",\n py: \"12px\",\n bgcolor: \"primary.50\",\n border: \"1px solid\",\n borderRadius: \"4px\",\n borderColor: \"primary.400\",\n alignItems: props.align ?? \"center\",\n gap: \"12px\",\n }}\n >\n <Box\n component={FiInfo}\n sx={{ width: \"20px\", height: \"20px\", color: \"primary.900\" }}\n />\n {props.children}\n <Box sx={{ flexGrow: 1 }} />\n <IconButton size=\"small\" onClick={props.onClose}>\n <IoClose />\n </IconButton>\n </Box>\n );\n};\n\nexport const LearnHowLink = () => {\n return (\n <MuiLink\n href=\"https://docs.reccehq.com/get-started/#prepare-dbt-artifacts\"\n target=\"_blank\"\n sx={{\n color: \"primary.main\",\n fontWeight: \"bold\",\n textDecoration: \"underline\",\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 0.5,\n }}\n >\n Learn how <LuExternalLink />\n </MuiLink>\n );\n};\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Stack from \"@mui/material/Stack\";\nimport { alpha } from \"@mui/material/styles\";\nimport Typography from \"@mui/material/Typography\";\nimport React, { useMemo } from \"react\";\nimport { FaPlay } from \"react-icons/fa6\";\nimport type { ManifestMetadata } from \"../../api\";\nimport {\n useLineageGraphContext,\n useRecceInstanceContext,\n} from \"../../contexts\";\nimport { useIsDark } from \"../../hooks\";\nimport { colors } from \"../../theme\";\nimport { extractSchemas, formatTimeToNow } from \"../../utils\";\nimport { CodeEditor } from \"../editor/CodeEditor\";\n\nexport interface SqlEditorProps {\n language?: string;\n theme?: string;\n value: string;\n baseValue?: string;\n onChange?: (value: string) => void;\n onChangeBase?: (value: string) => void;\n onRun?: () => void;\n onRunBase?: () => void;\n onRunDiff?: () => void;\n options?: {\n readOnly?: boolean;\n fontSize?: number;\n lineNumbers?: \"on\" | \"off\";\n wordWrap?: \"on\" | \"off\";\n };\n manifestData?: ManifestMetadata;\n schemas?: string;\n label?: string;\n CustomEditor?: React.ReactNode;\n}\n\nexport interface DualSqlEditorProps extends SqlEditorProps {\n labels?: [string, string]; // [baseLabel, currentLabel]\n SetupGuide?: React.ReactNode;\n}\n\nfunction SqlEditor({\n value,\n onChange,\n onRun,\n onRunBase,\n onRunDiff,\n label,\n CustomEditor,\n options = {},\n manifestData,\n schemas,\n ...props\n}: SqlEditorProps) {\n const { featureToggles } = useRecceInstanceContext();\n const isDark = useIsDark();\n\n const handleEditorChange = (value: string) => {\n if (onChange) {\n onChange(value);\n }\n };\n let timestamp = \"\";\n if (manifestData) {\n timestamp = manifestData.generated_at\n ? formatTimeToNow(manifestData.generated_at)\n : \"\";\n }\n\n // Convert keyboard shortcuts to CodeMirror format\n const keyBindings = useMemo(() => {\n const bindings = [];\n\n if (onRun) {\n bindings.push({\n key: \"Mod-Enter\", // Ctrl/Cmd + Enter\n run: () => {\n onRun();\n return true;\n },\n });\n }\n\n if (onRunBase) {\n bindings.push({\n key: \"Alt-Enter\",\n run: () => {\n onRunBase();\n return true;\n },\n });\n }\n\n if (onRunDiff) {\n bindings.push({\n key: \"Mod-Shift-Enter\", // Ctrl/Cmd + Shift + Enter\n run: () => {\n onRunDiff();\n return true;\n },\n });\n }\n\n return bindings;\n }, [onRun, onRunBase, onRunDiff]);\n\n // ... header rendering stays the same ...\n\n return (\n <>\n {(label ?? onRun ?? onRunBase) && (\n <Stack\n direction=\"row\"\n sx={{\n bgcolor: isDark\n ? alpha(colors.neutral[600], 0.7)\n : alpha(colors.neutral[200], 0.7),\n height: \"40px\",\n minHeight: \"40px\",\n fontSize: \"14px\",\n alignItems: \"center\",\n m: 0,\n p: \"0px 16px\",\n flex: \"0 0 40px\",\n }}\n >\n <Typography\n component=\"strong\"\n sx={{ fontWeight: \"bold\" }}\n className=\"no-track-pii-safe\"\n >\n {label ? label.toUpperCase() : \"\"}\n </Typography>\n {manifestData && (\n <span className=\"ml-1\">\n (\n {schemas && (\n <span className=\"no-track-pii-safe\">{schemas}, </span>\n )}\n <span>{timestamp}</span>)\n </span>\n )}\n\n <Box sx={{ flexGrow: 1 }} />\n {(onRun ?? onRunBase) && (\n <Button\n size=\"xsmall\"\n variant=\"outlined\"\n onClick={onRun ?? onRunBase}\n sx={{ bgcolor: \"background.paper\", p: \"6px 12px\" }}\n disabled={featureToggles.disableDatabaseQuery}\n startIcon={<FaPlay />}\n >\n Run Query\n </Button>\n )}\n </Stack>\n )}\n {CustomEditor ?? (\n <CodeEditor\n value={value}\n onChange={handleEditorChange}\n language=\"sql\"\n readOnly={options.readOnly ?? false}\n lineNumbers={options.lineNumbers !== \"off\"}\n wordWrap={options.wordWrap !== \"off\"}\n fontSize={options.fontSize ?? 16}\n keyBindings={keyBindings}\n theme={isDark ? \"dark\" : \"light\"}\n className=\"no-track-pii-safe max-h-dvh h-full\"\n />\n )}\n </>\n );\n}\n\nexport function DualSqlEditor({\n value,\n baseValue,\n onChange,\n onChangeBase,\n onRun,\n onRunBase,\n onRunDiff,\n options = {},\n labels,\n SetupGuide,\n ...props\n}: DualSqlEditorProps) {\n const baseLabel = labels ? labels[0] : \"Base\";\n const currentLabel = labels ? labels[1] : \"Current\";\n const { envInfo, lineageGraph } = useLineageGraphContext();\n\n let dbtBase: ManifestMetadata | undefined;\n let dbtCurrent: ManifestMetadata | undefined;\n if (envInfo?.dbt?.base && envInfo.dbt.current) {\n dbtBase = envInfo.dbt.base;\n dbtCurrent = envInfo.dbt.current;\n }\n\n const [baseSchemas, currentSchemas] = extractSchemas(lineageGraph);\n\n return (\n <>\n <Stack direction=\"row\" sx={{ height: \"100%\", gap: 0 }}>\n <Stack\n sx={{\n height: \"100%\",\n width: \"50%\",\n gap: 0,\n borderRight: \"1px solid\",\n borderRightColor: \"divider\",\n }}\n >\n <SqlEditor\n label={baseLabel}\n value={baseValue ?? \"\"}\n onChange={onChangeBase}\n onRunBase={onRunBase}\n options={options}\n CustomEditor={SetupGuide}\n manifestData={dbtBase ?? undefined}\n schemas={Array.from(baseSchemas).join(\", \")}\n {...props}\n />\n </Stack>\n <Stack sx={{ height: \"100%\", width: \"50%\", gap: 0 }}>\n <SqlEditor\n label={currentLabel}\n value={value}\n onChange={onChange}\n onRun={onRun}\n options={options}\n manifestData={dbtCurrent ?? undefined}\n schemas={Array.from(currentSchemas).join(\", \")}\n {...props}\n />\n </Stack>\n </Stack>\n </>\n );\n}\n\nexport default SqlEditor;\n","\"use client\";\n\n/**\n * @file run/RunResultPaneOss.tsx\n * @description OSS wrapper for RunResultPane component.\n *\n * This thin wrapper:\n * 1. Imports the base component from @datarecce/ui\n * 2. Injects OSS-specific context and behavior (tracking, clipboard, API client)\n *\n * OSS-specific behaviors injected:\n * - Analytics tracking (Amplitude via trackCopyToClipboard, trackShareState)\n * - Share state context (RecceShareStateContext)\n * - API client configuration (useApiConfig)\n * - Screenshot/clipboard functionality (useCopyToClipboardButton)\n * - CSV export functionality (useCSVExport)\n * - Run management (useRun hook)\n * - Navigation (useRouter)\n */\n\nimport Typography from \"@mui/material/Typography\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useRouter } from \"next/navigation\";\nimport { type Ref, useCallback, useState } from \"react\";\nimport {\n type AxiosQueryParams,\n cacheKeys,\n createCheckByRun,\n runTypeHasRef,\n} from \"../../api\";\nimport {\n useRecceActionContext,\n useRecceInstanceContext,\n useRouteConfig,\n} from \"../../contexts\";\nimport {\n useApiConfig,\n useCopyToClipboardButton,\n useCSVExport,\n useRecceShareStateContext,\n useRun,\n} from \"../../hooks\";\nimport { trackCopyToClipboard, trackShareState } from \"../../lib/api/track\";\nimport AuthModal from \"../app/AuthModal\";\nimport { LearnHowLink, RecceNotification } from \"../onboarding-guide\";\nimport { DualSqlEditor, SqlEditor } from \"../query\";\nimport { RunResultPane as BaseRunResultPane } from \"./RunResultPane\";\nimport { findByRunType } from \"./registry\";\nimport { RefTypes, RegistryEntry, ViewOptionTypes } from \"./types\";\n\n// ============================================================================\n// OSS Props Interface\n// ============================================================================\n\ninterface RunPageProps {\n onClose?: () => void;\n disableAddToChecklist?: boolean;\n isSingleEnvironment?: boolean;\n}\n\n// ============================================================================\n// Single Environment Notification Component (OSS-specific)\n// ============================================================================\n\nconst SingleEnvironmentSetupNotification = ({\n runType,\n onClose,\n}: {\n runType?: string;\n onClose: () => void;\n}) => {\n switch (runType) {\n case \"row_count\":\n return (\n <RecceNotification onClose={onClose}>\n <Typography>\n Enable row count diffing, and other Recce features, by configuring a\n base dbt environment to compare against. <LearnHowLink />\n </Typography>\n </RecceNotification>\n );\n case \"profile\":\n return (\n <RecceNotification onClose={onClose}>\n <Typography>\n Enable data-profile diffing, and other Recce features, by\n configuring a base dbt environment to compare against.{\" \"}\n <LearnHowLink />\n </Typography>\n </RecceNotification>\n );\n default:\n return null;\n }\n};\n\n// ============================================================================\n// SQL Editor Adapter Components\n// ============================================================================\n\ninterface SqlEditorAdapterProps {\n value: string;\n baseValue?: string;\n readOnly?: boolean;\n}\n\nconst SqlEditorAdapter = ({ value, readOnly }: SqlEditorAdapterProps) => (\n <SqlEditor value={value} options={{ readOnly }} />\n);\n\nconst DualSqlEditorAdapter = ({\n value,\n baseValue,\n readOnly,\n}: SqlEditorAdapterProps) => (\n <DualSqlEditor value={value} baseValue={baseValue} options={{ readOnly }} />\n);\n\n// ============================================================================\n// Auth Modal Adapter Component\n// ============================================================================\n\nconst AuthModalAdapter = ({\n open,\n onClose,\n}: {\n open: boolean;\n onClose: () => void;\n}) => (\n <AuthModal\n parentOpen={open}\n handleParentClose={() => onClose()}\n ignoreCookie\n variant=\"enable-share\"\n />\n);\n\n// ============================================================================\n// PrivateLoadableRunView - Main Implementation (OSS Wrapper)\n// ============================================================================\n\n/**\n * OSS implementation that loads run data and injects OSS-specific behavior\n * into the RunResultPane component.\n */\nexport const PrivateLoadableRunView = ({\n runId,\n onClose,\n isSingleEnvironment,\n}: {\n runId?: string;\n onClose?: () => void;\n isSingleEnvironment?: boolean;\n}) => {\n const { featureToggles, authed } = useRecceInstanceContext();\n const { runAction } = useRecceActionContext();\n const { error, run, onCancel, isRunning } = useRun(runId);\n const [viewOptions, setViewOptions] = useState<ViewOptionTypes>();\n const queryClient = useQueryClient();\n const router = useRouter();\n const { apiClient } = useApiConfig();\n const { basePath } = useRouteConfig();\n const { handleShareClick } = useRecceShareStateContext();\n\n // Get the result view component from registry\n let RunResultView: RegistryEntry[\"RunResultView\"] | undefined;\n if (run && runTypeHasRef(run.type)) {\n RunResultView = findByRunType(run.type)\n .RunResultView as RegistryEntry[\"RunResultView\"];\n }\n\n // Copy to clipboard functionality\n const { ref, onCopyToClipboard, onMouseEnter, onMouseLeave } =\n useCopyToClipboardButton();\n\n // CSV export functionality\n const csvExport = useCSVExport({\n run,\n viewOptions: viewOptions as Record<string, unknown>,\n });\n\n // Rerun handler\n const handleRerun = useCallback(() => {\n if (run) {\n runAction(run.type, run.params as unknown as AxiosQueryParams);\n }\n }, [run, runAction]);\n\n // Share to cloud handler\n const handleShareToCloud = useCallback(async () => {\n await handleShareClick();\n trackShareState({ name: \"create\" });\n }, [handleShareClick]);\n\n // Copy to clipboard handler with tracking\n const handleCopyAsImage = useCallback(async () => {\n await onCopyToClipboard();\n trackCopyToClipboard({\n type: run?.type ?? \"unknown\",\n from: \"run\",\n });\n }, [onCopyToClipboard, run?.type]);\n\n // Go to check handler\n const handleGoToCheck = useCallback(\n (checkId: string) => {\n router.push(`${basePath}/checks/?id=${checkId}`);\n },\n [router.push, basePath],\n );\n\n // Add to checklist handler\n const handleAddToChecklist = useCallback(async () => {\n if (!runId) {\n return;\n }\n const check = await createCheckByRun(\n runId,\n viewOptions as Record<string, unknown>,\n apiClient,\n );\n await queryClient.invalidateQueries({ queryKey: cacheKeys.checks() });\n router.push(`${basePath}/checks/?id=${check.check_id}`);\n }, [runId, viewOptions, apiClient, queryClient, router.push, basePath]);\n\n return (\n <BaseRunResultPane\n // Core data\n runId={runId}\n run={run}\n isRunning={isRunning}\n error={error}\n // View configuration\n viewOptions={viewOptions}\n onViewOptionsChanged={setViewOptions}\n isSingleEnvironment={isSingleEnvironment}\n // Feature toggles\n disableDatabaseQuery={featureToggles.disableDatabaseQuery}\n disableShare={featureToggles.disableShare}\n disableUpdateChecklist={featureToggles.disableUpdateChecklist}\n // Event handlers\n onClose={onClose}\n onCancel={onCancel}\n onRerun={handleRerun}\n // Export/Share handlers\n onCopyAsImage={handleCopyAsImage}\n onCopyMouseEnter={onMouseEnter}\n onCopyMouseLeave={onMouseLeave}\n csvExport={csvExport}\n authed={authed}\n onShareToCloud={handleShareToCloud}\n // Checklist handlers\n onGoToCheck={handleGoToCheck}\n onAddToChecklist={handleAddToChecklist}\n // Custom components (OSS-specific)\n SingleEnvironmentNotification={SingleEnvironmentSetupNotification}\n SqlEditorComponent={SqlEditorAdapter}\n DualSqlEditorComponent={DualSqlEditorAdapter}\n AuthModalComponent={AuthModalAdapter}\n RunResultView={RunResultView}\n resultViewRef={ref as Ref<RefTypes>}\n />\n );\n};\n\n// ============================================================================\n// RunResultPaneOss - Public Component (OSS Wrapper)\n// ============================================================================\n\n/**\n * OSS RunResultPane Component\n *\n * A thin wrapper around RunResultPane that injects OSS-specific\n * context and behavior including:\n * - Analytics tracking (Amplitude)\n * - Share state context\n * - API client configuration\n * - Screenshot/clipboard functionality\n * - CSV export functionality\n *\n * @example\n * ```tsx\n * import { RunResultPaneOss } from \"@datarecce/ui/components/run\";\n *\n * function MyRunView() {\n * return (\n * <RunResultPaneOss\n * onClose={() => handleClose()}\n * isSingleEnvironment={isSingleEnv}\n * />\n * );\n * }\n * ```\n */\nexport const RunResultPaneOss = ({\n onClose,\n isSingleEnvironment,\n}: RunPageProps) => {\n const { runId } = useRecceActionContext();\n\n return (\n <PrivateLoadableRunView\n runId={runId}\n onClose={onClose}\n isSingleEnvironment={isSingleEnvironment}\n />\n );\n};\n","import Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Typography from \"@mui/material/Typography\";\nimport {\n FallbackRender,\n ErrorBoundary as SentryErrorBoundary,\n} from \"@sentry/react\";\nimport * as React from \"react\";\nimport { ReactNode } from \"react\";\nimport { useThemeColors } from \"../../hooks\";\n\n/**\n * Fallback component that renders when an error is caught.\n * Uses useThemeColors for theme-aware styling.\n */\nfunction FallbackComponent({\n error,\n resetError,\n}: {\n error: Error;\n resetError: () => void;\n}) {\n const { background, text } = useThemeColors();\n\n return (\n <Box\n sx={{\n height: \"100%\",\n bgcolor: background.subtle,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <Box\n sx={{\n p: 4,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-start\",\n bgcolor: \"background.paper\",\n border: \"solid 1px\",\n borderColor: \"divider\",\n minHeight: \"200px\",\n }}\n >\n <Typography variant=\"h6\" sx={{ width: \"800px\" }}>\n You have encountered an error\n </Typography>\n\n <Box sx={{ flex: 1, fontSize: \"10pt\", color: text.secondary }}>\n {String(error)}\n </Box>\n\n <Button\n sx={{\n justifySelf: \"center\",\n alignSelf: \"center\",\n mt: \"20px\",\n }}\n color=\"iochmara\"\n variant=\"contained\"\n size=\"small\"\n onClick={resetError}\n >\n Reset\n </Button>\n </Box>\n </Box>\n );\n}\n\n/**\n * Wrapper function that converts the component to Sentry's FallbackRender format.\n */\nconst Fallback: FallbackRender = (errorData) => {\n return (\n <FallbackComponent\n error={errorData.error as Error}\n resetError={errorData.resetError}\n />\n );\n};\n\nexport const ErrorBoundary = ({\n children,\n fallback = Fallback,\n}: {\n children: ReactNode;\n fallback?: React.ReactElement | FallbackRender | undefined;\n}) => {\n return (\n <SentryErrorBoundary fallback={fallback}>{children}</SentryErrorBoundary>\n );\n};\n","\"use client\";\n\n/**\n * @file run/RunViewOss.tsx\n * @description OSS wrapper for RunView component with Sentry error boundary injection.\n *\n * This thin wrapper imports the base RunView from @datarecce/ui and injects\n * OSS-specific dependencies:\n * - ErrorBoundary: Sentry error boundary for error tracking\n * - ResultErrorFallback: Error fallback component for run results\n *\n * @example\n * ```tsx\n * import { RunViewOss } from \"@datarecce/ui/components/run\";\n * import { QueryResultView } from \"./QueryResultView\";\n *\n * function MyComponent() {\n * return (\n * <RunViewOss\n * run={run}\n * isRunning={isRunning}\n * RunResultView={QueryResultView}\n * onCancel={handleCancel}\n * />\n * );\n * }\n * ```\n */\n\nimport type { ReactNode, Ref } from \"react\";\nimport { forwardRef } from \"react\";\nimport type { Run } from \"../../api\";\nimport ResultErrorFallback from \"../../lib/result/ResultErrorFallback\";\nimport { ErrorBoundary } from \"../errorboundary\";\nimport {\n RunView as BaseRunView,\n type RunViewProps as BaseRunViewProps,\n} from \"./RunView\";\nimport {\n RefTypes,\n RegistryEntry,\n RunResultViewProps,\n ViewOptionTypes,\n} from \"./types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * OSS-specific RunView props using OSS types for backward compatibility.\n *\n * @typeParam VO - View options type (defaults to ViewOptionTypes union)\n */\nexport interface RunViewOssProps<VO = ViewOptionTypes> {\n /** Whether a run is currently executing */\n isRunning?: boolean;\n\n /** The run object containing execution state and results */\n run?: Run;\n\n /** Error that occurred during run execution */\n error?: Error | null;\n\n /** Progress information for the current run */\n progress?: Run[\"progress\"];\n\n /** Whether the run is being aborted */\n isAborting?: boolean;\n\n /**\n * Whether this is a check detail view.\n * @deprecated This prop may be removed in future versions.\n */\n isCheckDetail?: boolean;\n\n /** Callback when user cancels the run */\n onCancel?: () => void;\n\n /** Callback to execute/re-execute the run */\n onExecuteRun?: () => void;\n\n /** Current view options for result display */\n viewOptions?: VO;\n\n /** Callback when view options change */\n onViewOptionsChanged?: (viewOptions: VO) => void;\n\n /**\n * Component to render run results.\n * Either RunResultView or children is required.\n */\n RunResultView?: RegistryEntry[\"RunResultView\"];\n\n /**\n * Render prop for custom result rendering.\n * Either RunResultView or children is required.\n */\n children?: (params: RunResultViewProps<ViewOptionTypes>) => ReactNode;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * OSS RunView component with Sentry error boundary pre-injected.\n *\n * This is a thin wrapper around the base RunView from @datarecce/ui that\n * injects OSS-specific error handling via Sentry.\n *\n * States:\n * 1. **Error state**: Shows error message from API response or run.error\n * 2. **Running state**: Shows loading spinner with progress and cancel button\n * 3. **Loading state**: Shows spinner when run is undefined\n * 4. **Result state**: Renders RunResultView or children with run results,\n * wrapped in Sentry error boundary for crash reporting\n *\n * @example\n * ```tsx\n * const ref = useRef<DataGridHandle>(null);\n *\n * <RunViewOss\n * ref={ref}\n * run={run}\n * RunResultView={QueryResultView}\n * viewOptions={viewOptions}\n * onViewOptionsChanged={setViewOptions}\n * />\n * ```\n */\nexport const RunViewOss = forwardRef<RefTypes, RunViewOssProps>(\n function RunViewOss(props, ref) {\n // Cast to base props for compatibility\n const baseProps: BaseRunViewProps = {\n ...props,\n ErrorBoundary,\n errorBoundaryFallback: ResultErrorFallback,\n };\n\n return <BaseRunView {...baseProps} ref={ref as Ref<unknown>} />;\n },\n);\n\n// Set display name for debugging\nRunViewOss.displayName = \"RunViewOss\";\n","/**\n * @file useMultiNodesAction.ts\n * @description Hook for executing batch operations on multiple lineage graph nodes.\n *\n * This hook provides functionality for running data validation operations on\n * selected nodes in the lineage graph. It supports two execution modes:\n *\n * 1. **multi_nodes mode** - A single run is submitted for all nodes at once\n * (used for row_count, row_count_diff)\n *\n * 2. **per_node mode** - Individual runs are submitted sequentially per node\n * (used for value_diff which requires primary key per model)\n *\n * The hook manages action state, handles run polling, cancellation, and provides\n * callbacks for UI updates during execution.\n *\n * @example\n * ```tsx\n * const { actionState, runRowCount, runRowCountDiff, cancel, reset } = useMultiNodesAction(\n * selectedNodes,\n * {\n * onActionStarted: () => setIsRunning(true),\n * onActionNodeUpdated: (node) => updateNodeUI(node),\n * onActionCompleted: () => setIsRunning(false),\n * }\n * );\n * ```\n */\n\nimport { useRef } from \"react\";\nimport {\n type Check,\n cancelRun,\n createLineageDiffCheck,\n createSchemaDiffCheck,\n type RowCountDiffParams,\n type RowCountParams,\n type Run,\n type RunType,\n submitRun,\n type ValueDiffParams,\n waitRun,\n} from \"../api\";\nimport { useRecceActionContext } from \"../contexts/action\";\nimport type { ActionState, LineageGraphNode } from \"../contexts/lineage/types\";\nimport { useApiClient } from \"../providers\";\n\n/**\n * Action types that can be tracked.\n * Maps to common data validation operations.\n */\nexport type MultiNodesActionType =\n | \"row_count\"\n | \"row_count_diff\"\n | \"value_diff\";\n\n/**\n * Properties passed to the tracking callback when an action is executed.\n */\nexport interface MultiNodesActionTrackProps {\n /** The type of action being executed */\n action: MultiNodesActionType;\n /** Source/location where the action was triggered */\n source: string;\n /** Number of nodes included in the action */\n node_count: number;\n}\n\n/**\n * Callbacks for tracking analytics events during action execution.\n * This is intentionally optional to allow the base hook to be used\n * in environments without analytics (e.g., embedded or library usage).\n */\nexport interface MultiNodesActionTracking {\n /**\n * Called when an action is executed.\n * Implementations can use this to send analytics events.\n * @param props - Properties describing the action\n */\n onTrackAction?: (props: MultiNodesActionTrackProps) => void;\n}\n\n/**\n * Lifecycle callbacks for action execution.\n * These are called at key points during action execution to allow\n * the UI to respond to state changes.\n */\nexport interface MultiNodesActionCallbacks {\n /** Called when any action starts executing */\n onActionStarted: () => void;\n /** Called when a node's action state is updated (status change, completion, etc.) */\n onActionNodeUpdated: (node: LineageGraphNode) => void;\n /** Called when all actions in the batch complete (success, failure, or cancellation) */\n onActionCompleted: () => void;\n}\n\n/**\n * Configuration options for the useMultiNodesAction hook.\n */\nexport interface UseMultiNodesActionOptions\n extends MultiNodesActionCallbacks,\n MultiNodesActionTracking {\n /**\n * Source identifier for tracking where actions are triggered from.\n * Used in analytics events.\n * @default \"lineage_view_top_bar\"\n */\n trackingSource?: string;\n}\n\n/**\n * Return type for the useMultiNodesAction hook.\n */\nexport interface UseMultiNodesActionReturn {\n /** Current action state (mutable ref for performance) */\n actionState: ActionState;\n /** Execute row count on selected nodes (multi_nodes mode) */\n runRowCount: () => Promise<void>;\n /** Execute row count diff on selected nodes (multi_nodes mode) */\n runRowCountDiff: () => Promise<void>;\n /** Execute value diff on selected nodes (per_node mode) */\n runValueDiff: () => Promise<void>;\n /** Create a lineage diff check for selected nodes */\n addLineageDiffCheck: () => Promise<Check>;\n /** Create a schema diff check for selected nodes */\n addSchemaDiffCheck: () => Promise<Check>;\n /** Cancel the current running action */\n cancel: () => Promise<void>;\n /** Reset action state to initial values */\n reset: () => void;\n}\n\n/**\n * Initial state for action state object.\n * This is reset when the hook initializes or reset() is called.\n */\nconst initValue: ActionState = {\n mode: \"per_node\",\n status: \"pending\",\n completed: 0,\n total: 0,\n actions: {},\n};\n\n/**\n * Hook for executing batch operations on multiple lineage graph nodes.\n *\n * Provides methods to run data validation operations (row count, row count diff,\n * value diff) on selected nodes, with support for action tracking, cancellation,\n * and progress monitoring.\n *\n * @param nodes - Array of lineage graph nodes to operate on\n * @param options - Configuration options including callbacks and tracking\n * @returns Object containing action state and operation methods\n *\n * @example\n * ```tsx\n * function ActionBar({ selectedNodes }) {\n * const {\n * actionState,\n * runRowCount,\n * runRowCountDiff,\n * runValueDiff,\n * cancel,\n * reset\n * } = useMultiNodesAction(selectedNodes, {\n * onActionStarted: () => console.log('Started'),\n * onActionNodeUpdated: (node) => console.log('Updated:', node.id),\n * onActionCompleted: () => console.log('Completed'),\n * onTrackAction: (props) => analytics.track(props),\n * });\n *\n * return (\n * <div>\n * <button onClick={runRowCount}>Row Count</button>\n * <button onClick={cancel}>Cancel</button>\n * <span>{actionState.status}</span>\n * </div>\n * );\n * }\n * ```\n */\nexport const useMultiNodesAction = (\n nodes: LineageGraphNode[],\n options: UseMultiNodesActionOptions,\n): UseMultiNodesActionReturn => {\n const {\n onActionStarted,\n onActionNodeUpdated,\n onActionCompleted,\n onTrackAction,\n trackingSource = \"lineage_view_top_bar\",\n } = options;\n\n const apiClient = useApiClient();\n const actionState = useRef<ActionState>({\n ...initValue,\n }).current;\n\n const { showRunId } = useRecceActionContext();\n\n /**\n * Submit a single run for multiple nodes (multi_nodes mode).\n * Used by row_count and row_count_diff operations.\n */\n const submitRunForNodes = async (\n type: RunType,\n skip: (node: LineageGraphNode) => string | undefined,\n getParams: (\n nodes: LineageGraphNode[],\n ) => RowCountParams | RowCountDiffParams,\n ) => {\n actionState.mode = \"multi_nodes\";\n actionState.actions = {};\n const mode = actionState.mode;\n const actions = actionState.actions;\n\n onActionStarted();\n actionState.status = \"running\";\n\n const candidates: LineageGraphNode[] = [];\n\n for (const node of nodes) {\n const skipReason = skip(node);\n\n if (skipReason) {\n actions[node.id] = {\n mode,\n status: \"skipped\",\n skipReason,\n };\n onActionNodeUpdated(node);\n } else {\n actions[node.id] = { mode, status: \"pending\" };\n candidates.push(node);\n }\n }\n\n const params = getParams(candidates);\n\n try {\n const { run_id } = await submitRun(\n type,\n params,\n { nowait: true },\n apiClient,\n );\n showRunId(run_id);\n actionState.currentRun = { run_id };\n actionState.total = 1;\n\n for (;;) {\n const run = (await waitRun(run_id, 2, apiClient)) as Run;\n actionState.currentRun = run;\n\n const status = run.error\n ? \"failure\"\n : run.result\n ? \"success\"\n : \"running\";\n\n for (const node of candidates) {\n actions[node.id] = {\n mode,\n status,\n run,\n };\n onActionNodeUpdated(node);\n }\n\n if (run.error || run.result) {\n break;\n }\n }\n } catch (_e) {\n // Error handling is done via actionState, no need to throw\n }\n\n actionState.completed = 1;\n if ((actionState.status as string) === \"canceling\") {\n actionState.status = \"canceled\";\n onActionCompleted();\n return;\n }\n\n actionState.status = \"completed\";\n onActionCompleted();\n };\n\n /**\n * Submit individual runs for each node sequentially (per_node mode).\n * Used by value_diff operation which requires per-model primary keys.\n */\n const submitRunsPerNodes = async (\n type: RunType,\n getParams: (node: LineageGraphNode) => {\n params?: ValueDiffParams;\n skipReason?: string;\n },\n ) => {\n actionState.mode = \"per_node\";\n actionState.actions = {};\n const mode = actionState.mode;\n const actions = actionState.actions;\n\n onActionStarted();\n actionState.status = \"running\";\n\n for (const node of nodes) {\n actions[node.id] = { mode, status: \"pending\" };\n onActionNodeUpdated(node);\n }\n\n actionState.completed = 0;\n actionState.total = nodes.length;\n\n for (const node of nodes) {\n const { params, skipReason } = getParams(node);\n if (skipReason) {\n actions[node.id] = {\n mode,\n status: \"skipped\",\n skipReason,\n };\n onActionNodeUpdated(node);\n } else {\n try {\n const { run_id } = await submitRun(\n type,\n params,\n { nowait: true },\n apiClient,\n );\n actionState.currentRun = { run_id };\n actions[node.id] = {\n mode,\n status: \"running\",\n };\n onActionNodeUpdated(node);\n\n for (;;) {\n const run = (await waitRun(run_id, 2, apiClient)) as Run;\n actionState.currentRun = run;\n const status = run.error\n ? \"failure\"\n : run.result\n ? \"success\"\n : \"running\";\n actions[node.id] = {\n mode,\n status,\n run,\n };\n onActionNodeUpdated(node);\n\n if (run.error || run.result) {\n break;\n }\n }\n } catch (_e) {\n // Error handling is done via actionState\n } finally {\n actionState.currentRun = undefined;\n }\n }\n actionState.completed++;\n if ((actionState.status as string) === \"canceling\") {\n actionState.status = \"canceled\";\n onActionCompleted();\n return;\n }\n }\n\n actionState.status = \"completed\";\n onActionCompleted();\n };\n\n /**\n * Execute row count on all selected model nodes.\n * Non-model nodes are skipped.\n */\n const runRowCount = async () => {\n onTrackAction?.({\n action: \"row_count\",\n source: trackingSource,\n node_count: nodes.length,\n });\n\n // Pre-mark non-model nodes as skipped for immediate UI feedback\n for (const node of nodes) {\n if (node.data.resourceType !== \"model\") {\n actionState.actions[node.id] = {\n mode: \"multi_nodes\",\n status: \"skipped\",\n skipReason: \"Not a model\",\n };\n onActionNodeUpdated(node);\n }\n }\n\n const skip = (node: LineageGraphNode) => {\n if (node.data.resourceType !== \"model\") {\n return \"Not a model\";\n }\n };\n const getParams = (nodes: LineageGraphNode[]): RowCountParams => {\n return {\n node_names: nodes.map((node) => node.data.name),\n };\n };\n\n await submitRunForNodes(\"row_count\", skip, getParams);\n };\n\n /**\n * Execute row count diff on all selected model nodes.\n * Non-model nodes are skipped.\n */\n const runRowCountDiff = async () => {\n onTrackAction?.({\n action: \"row_count_diff\",\n source: trackingSource,\n node_count: nodes.length,\n });\n\n // Pre-mark non-model nodes as skipped for immediate UI feedback\n for (const node of nodes) {\n if (node.data.resourceType !== \"model\") {\n actionState.actions[node.id] = {\n mode: \"multi_nodes\",\n status: \"skipped\",\n skipReason: \"Not a model\",\n };\n onActionNodeUpdated(node);\n }\n }\n\n const skip = (node: LineageGraphNode) => {\n if (node.data.resourceType !== \"model\") {\n return \"Not a model\";\n }\n };\n const getParams = (nodes: LineageGraphNode[]): RowCountDiffParams => {\n return {\n node_names: nodes.map((node) => node.data.name),\n };\n };\n\n await submitRunForNodes(\"row_count_diff\", skip, getParams);\n };\n\n /**\n * Execute value diff on all selected nodes that have primary keys.\n * Nodes without primary keys are skipped.\n */\n const runValueDiff = async () => {\n onTrackAction?.({\n action: \"value_diff\",\n source: trackingSource,\n node_count: nodes.length,\n });\n\n await submitRunsPerNodes(\"value_diff\", (node) => {\n const primaryKey = node.data.data.current?.primary_key;\n if (!primaryKey) {\n return {\n skipReason:\n \"No primary key found. The first unique column is used as primary key.\",\n };\n }\n\n const params: ValueDiffParams = {\n model: node.data.name,\n primary_key: primaryKey,\n };\n\n return { params };\n });\n };\n\n /**\n * Create a lineage diff check for the selected nodes.\n * @returns The created check object\n */\n const addLineageDiffCheck = async () => {\n const nodeIds = nodes.map((node) => node.id);\n return await createLineageDiffCheck(\n {\n node_ids: nodeIds,\n },\n apiClient,\n );\n };\n\n /**\n * Create a schema diff check for the selected nodes.\n * Handles both single node and multi-node scenarios.\n * @returns The created check object\n */\n const addSchemaDiffCheck = async () => {\n let check;\n if (nodes.length === 1) {\n check = await createSchemaDiffCheck({ node_id: nodes[0].id }, apiClient);\n } else {\n const nodeIds = nodes.map((node) => node.id);\n check = await createSchemaDiffCheck({ node_id: nodeIds }, apiClient);\n }\n return check;\n };\n\n /**\n * Cancel the current running action.\n * Sets status to \"canceling\" and calls cancelRun API if a run is in progress.\n */\n const cancel = async () => {\n actionState.status = \"canceling\";\n if (actionState.currentRun?.run_id) {\n await cancelRun(actionState.currentRun.run_id, apiClient);\n }\n };\n\n /**\n * Reset action state to initial values.\n * Call this before starting a new action to clear previous state.\n */\n const reset = () => {\n Object.assign(actionState, initValue);\n };\n\n return {\n actionState,\n runRowCount,\n runRowCountDiff,\n runValueDiff,\n addLineageDiffCheck,\n addSchemaDiffCheck,\n cancel,\n reset,\n };\n};\n","\"use client\";\n\n/**\n * @file useValueDiffAlertDialog.tsx\n * @description Hook for displaying a value diff confirmation dialog.\n *\n * This hook provides a callback-based API for confirming value diff operations\n * on multiple nodes. The actual tracking/analytics can be injected via callbacks.\n */\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Stack from \"@mui/material/Stack\";\nimport type { JSX } from \"react\";\nimport { useCallback, useRef, useState } from \"react\";\nimport { IoClose } from \"react-icons/io5\";\n\n/**\n * Options for useValueDiffAlertDialog hook\n */\nexport interface UseValueDiffAlertDialogOptions {\n /** Callback invoked when user confirms the value diff operation */\n onConfirm?: (nodeCount: number) => void;\n /** Callback invoked when user cancels the value diff operation */\n onCancel?: (nodeCount: number) => void;\n}\n\n/**\n * Return type for useValueDiffAlertDialog hook\n */\nexport interface UseValueDiffAlertDialogReturn {\n /** Function to trigger the confirmation dialog. Returns a promise that resolves to true if confirmed, false if cancelled. */\n confirm: (nodeCount: number) => Promise<boolean>;\n /** The dialog component to render in your component tree */\n AlertDialog: JSX.Element;\n}\n\n/**\n * Hook for displaying a confirmation dialog before executing value diff on multiple nodes.\n *\n * @param options - Optional callbacks for tracking/analytics\n * @returns Object containing `confirm` function and `AlertDialog` component\n *\n * @example Basic usage without tracking\n * ```tsx\n * import { useValueDiffAlertDialog } from '@datarecce/ui/hooks';\n *\n * function MyComponent() {\n * const { confirm, AlertDialog } = useValueDiffAlertDialog();\n *\n * const handleValueDiff = async () => {\n * const confirmed = await confirm(5); // 5 nodes\n * if (confirmed) {\n * // Execute value diff\n * }\n * };\n *\n * return (\n * <>\n * <button onClick={handleValueDiff}>Run Value Diff</button>\n * {AlertDialog}\n * </>\n * );\n * }\n * ```\n *\n * @example With tracking callbacks\n * ```tsx\n * import { useValueDiffAlertDialog } from '@datarecce/ui/hooks';\n *\n * function MyComponent() {\n * const { confirm, AlertDialog } = useValueDiffAlertDialog({\n * onConfirm: (nodeCount) => {\n * analytics.track('value_diff_confirmed', { nodeCount });\n * },\n * onCancel: (nodeCount) => {\n * analytics.track('value_diff_cancelled', { nodeCount });\n * },\n * });\n *\n * return (\n * <>\n * <button onClick={() => confirm(10)}>Run Value Diff</button>\n * {AlertDialog}\n * </>\n * );\n * }\n * ```\n */\nexport function useValueDiffAlertDialog(\n options?: UseValueDiffAlertDialogOptions,\n): UseValueDiffAlertDialogReturn {\n const [open, setOpen] = useState(false);\n const [nodeCount, setNodeCount] = useState(0);\n const [resolvePromise, setResolvePromise] =\n useState<(value: boolean) => void>();\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n const confirm = useCallback((count: number) => {\n setNodeCount(count);\n return new Promise<boolean>((resolve) => {\n setResolvePromise(() => resolve);\n setOpen(true);\n });\n }, []);\n\n const handleConfirm = () => {\n options?.onConfirm?.(nodeCount);\n resolvePromise?.(true);\n setOpen(false);\n };\n\n const handleCancel = () => {\n options?.onCancel?.(nodeCount);\n resolvePromise?.(false);\n setOpen(false);\n };\n\n const AlertDialog = (\n <MuiDialog\n open={open}\n onClose={handleCancel}\n maxWidth=\"md\"\n fullWidth\n aria-labelledby=\"value-diff-alert-dialog-title\"\n >\n <DialogTitle\n id=\"value-diff-alert-dialog-title\"\n sx={{ fontSize: \"1.125rem\", fontWeight: \"bold\" }}\n >\n Value Diff on {nodeCount} nodes\n </DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={handleCancel}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n <DialogContent>\n <Stack spacing=\"20px\">\n <Box>\n Value diff will be executed on {nodeCount} nodes in the Lineage,\n which can add extra costs to your bill.\n </Box>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ gap: 0.5 }}>\n <Button\n ref={cancelRef}\n onClick={handleCancel}\n variant=\"outlined\"\n color=\"neutral\"\n >\n Cancel\n </Button>\n <Button\n color=\"iochmara\"\n variant=\"contained\"\n onClick={handleConfirm}\n sx={{ ml: 1.5 }}\n >\n Execute\n </Button>\n </DialogActions>\n </MuiDialog>\n );\n\n return { confirm, AlertDialog };\n}\n","\"use client\";\n\nimport Button from \"@mui/material/Button\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport Typography from \"@mui/material/Typography\";\nimport type { ComponentType, ReactNode } from \"react\";\nimport type { RecceFeatureMode } from \"../../contexts/instance\";\nimport { formatDuration } from \"../../utils/formatTime\";\n\n/**\n * Props for the ServerDisconnectedModalContent component\n */\nexport interface ServerDisconnectedModalContentProps {\n /** Callback to attempt reconnection */\n connect: () => void;\n /** If provided, indicates the server was idle for this many seconds before timeout */\n idleSeconds?: number | null;\n}\n\n/**\n * Modal content displayed when the local server connection is lost.\n * Shows different messages for idle timeout vs unexpected disconnection.\n */\nexport function ServerDisconnectedModalContent({\n connect,\n idleSeconds,\n}: ServerDisconnectedModalContentProps) {\n const isIdleTimeout =\n idleSeconds !== undefined && idleSeconds !== null && idleSeconds > 0;\n\n return (\n <>\n <DialogTitle>Server Disconnected</DialogTitle>\n <DialogContent>\n {isIdleTimeout ? (\n <Typography>\n The server has been idle for {formatDuration(idleSeconds)} and was\n automatically stopped. Please restart the Recce server to continue.\n </Typography>\n ) : (\n <Typography>\n The server connection has been lost. Please restart the Recce server\n and try again.\n </Typography>\n )}\n </DialogContent>\n <DialogActions>\n <Button\n color=\"iochmara\"\n variant=\"contained\"\n onClick={() => {\n connect();\n }}\n >\n Retry\n </Button>\n </DialogActions>\n </>\n );\n}\n\n/**\n * Props for a link component that can wrap children\n */\nexport interface LinkComponentProps {\n href: string;\n children: ReactNode;\n}\n\n/**\n * Props for the RecceInstanceDisconnectedModalContent component\n */\nexport interface RecceInstanceDisconnectedModalContentProps {\n /** URL to restart the share instance */\n shareUrl: string;\n /** The feature mode (determines which message to display) */\n mode: Exclude<RecceFeatureMode, null>;\n /** URL for scheduling support calls (used in metadata-only mode) */\n supportCalendarUrl: string;\n /**\n * Optional link component for routing integration (e.g., Next.js Link).\n * If provided, used for \"read only\" mode navigation.\n * Should pass `href` to child and handle routing.\n */\n LinkComponent?: ComponentType<LinkComponentProps>;\n}\n\n/**\n * Modal content displayed when a cloud instance expires.\n * Shows different messages based on the feature mode:\n * - \"read only\": Share instance expired, offers restart\n * - \"metadata only\": Preview instance expired, offers contact option\n */\nexport function RecceInstanceDisconnectedModalContent({\n shareUrl,\n mode,\n supportCalendarUrl,\n LinkComponent,\n}: RecceInstanceDisconnectedModalContentProps) {\n const contents = {\n \"read only\": {\n title: \"Share Instance Expired\",\n body: \"This Share Instance has expired. Please restart the share instance.\",\n action: \"Restart\",\n link: shareUrl,\n },\n \"metadata only\": {\n title: \"Preview Instance Expired\",\n body: \"This Preview Instance has expired. To browse more, please book a meeting with us.\",\n action: \"Contact us\",\n link: supportCalendarUrl,\n },\n };\n\n const content = contents[mode];\n\n const button = (\n <Button color=\"iochmara\" variant=\"contained\">\n {content.action}\n </Button>\n );\n\n return (\n <>\n <DialogTitle>{content.title}</DialogTitle>\n <DialogContent>\n <Typography>{content.body}</Typography>\n </DialogContent>\n <DialogActions>\n {mode === \"read only\" ? (\n LinkComponent ? (\n <LinkComponent href={content.link}>{button}</LinkComponent>\n ) : (\n <a href={content.link}>{button}</a>\n )\n ) : (\n <Button\n color=\"iochmara\"\n variant=\"contained\"\n onClick={() => window.open(content.link, \"_blank\")}\n >\n {content.action}\n </Button>\n )}\n </DialogActions>\n </>\n );\n}\n","\"use client\";\n\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Typography from \"@mui/material/Typography\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport NextLink from \"next/link\";\nimport React, {\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { IoClose } from \"react-icons/io5\";\nimport {\n aggregateRuns,\n cacheKeys,\n getServerInfo,\n markRelaunchHintCompleted,\n} from \"../api\";\nimport {\n RecceInstanceDisconnectedModalContent,\n ServerDisconnectedModalContent,\n} from \"../components/lineage\";\nimport { toaster } from \"../components/ui/Toaster\";\nimport {\n buildLineageGraph,\n type EnvInfo,\n LineageGraphProvider,\n useIdleTimeout,\n useRecceInstanceContext,\n useRecceServerFlag,\n} from \"../contexts\";\nimport { trackSingleEnvironment } from \"../lib/api/track\";\nimport { PUBLIC_API_URL, RECCE_SUPPORT_CALENDAR_URL } from \"../lib/const\";\nimport { useApiConfig } from \"./useApiConfig\";\n\ntype LineageWatcherStatus = \"pending\" | \"connected\" | \"disconnected\";\ntype EnvWatcherStatus = undefined | \"relaunch\";\n\ninterface WebSocketRefreshEvent {\n eventType: \"created\" | \"updated\" | \"deleted\";\n srcPath: string;\n}\n\ninterface WebSocketBroadcastEvent {\n id: string;\n title?: string;\n description: string;\n status?: \"info\" | \"warning\" | \"success\" | \"error\";\n position?:\n | \"top\"\n | \"top-right\"\n | \"top-left\"\n | \"bottom\"\n | \"bottom-right\"\n | \"bottom-left\";\n duration?: number;\n}\n\ntype WebSocketPayload =\n | {\n command: \"refresh\";\n event: WebSocketRefreshEvent;\n }\n | {\n command: \"relaunch\";\n }\n | {\n command: \"broadcast\";\n event: WebSocketBroadcastEvent;\n };\n\ninterface UseLineageWatcherOptions {\n /**\n * Whether to enable WebSocket connection.\n * Set to false for cloud mode where WebSocket is not used.\n */\n enabled?: boolean;\n\n /**\n * Base URL for WebSocket connection.\n * If not provided, uses PUBLIC_API_URL.\n */\n baseUrl?: string;\n\n /**\n * API prefix to replace /api in WebSocket URL.\n * If not provided, uses default /api/ws path.\n */\n apiPrefix?: string;\n}\n\nfunction useLineageWatcher({\n enabled = true,\n baseUrl,\n apiPrefix,\n}: UseLineageWatcherOptions = {}) {\n const [artifactsUpdatedToastId, setArtifactsUpdatedToastId] = useState<\n string | undefined\n >(undefined);\n\n // use ref so that the callbacks can access the latest values\n const ref = useRef<{\n ws: WebSocket | undefined;\n status: LineageWatcherStatus;\n artifactsUpdatedToastId: string | undefined;\n }>({\n ws: undefined,\n status: \"pending\",\n artifactsUpdatedToastId: undefined,\n });\n\n // If disabled, always return \"connected\" status to avoid showing disconnect modal\n const [status, setStatus] = useState<LineageWatcherStatus>(\n enabled ? \"pending\" : \"connected\",\n );\n const [envStatus, setEnvStatus] = useState<EnvWatcherStatus>(undefined);\n\n // Update ref in useEffect to avoid updating during render\n useEffect(() => {\n ref.current.status = status;\n }, [status]);\n\n // Keep artifactsUpdatedToastId in sync with ref\n useEffect(() => {\n ref.current.artifactsUpdatedToastId = artifactsUpdatedToastId;\n }, [artifactsUpdatedToastId]);\n\n const queryClient = useQueryClient();\n\n const invalidateCaches = useCallback(() => {\n void queryClient.invalidateQueries({ queryKey: cacheKeys.lineage() });\n void queryClient.invalidateQueries({ queryKey: cacheKeys.checks() });\n void queryClient.invalidateQueries({ queryKey: cacheKeys.runs() });\n }, [queryClient]);\n\n const connect = useCallback(() => {\n function httpUrlToWebSocketUrl(url: string): string {\n return url.replace(/(http)(s)?:\\/\\//, \"ws$2://\");\n }\n\n // Use baseUrl if provided, otherwise fall back to PUBLIC_API_URL\n const effectiveBaseUrl = baseUrl ?? PUBLIC_API_URL;\n // Construct WebSocket path with apiPrefix if provided\n const wsPath = apiPrefix ? `${apiPrefix}/ws` : \"/api/ws\";\n const ws = new WebSocket(\n `${httpUrlToWebSocketUrl(effectiveBaseUrl)}${wsPath}`,\n );\n ref.current.ws = ws;\n\n ws.onopen = () => {\n ws.send(\"ping\"); // server will respond with 'pong'\n };\n\n // Handling websocket messages from the server\n ws.onmessage = (event) => {\n if (event.data === \"pong\") {\n if (ref.current.status === \"disconnected\") {\n invalidateCaches();\n }\n setStatus(\"connected\");\n return;\n }\n try {\n const data = JSON.parse(event.data as string) as WebSocketPayload;\n if (data.command === \"refresh\") {\n const { eventType, srcPath } = data.event;\n const [targetName, fileName] = srcPath.split(\"/\").slice(-2);\n // Extract filename without extension (browser-compatible alternative to path.parse)\n const name = fileName.replace(/\\.[^/.]+$/, \"\");\n const eventId = `${targetName}-${name}-${eventType}`;\n if (ref.current.artifactsUpdatedToastId == null) {\n setArtifactsUpdatedToastId(\n toaster.create({\n id: eventId,\n description: `Detected ${targetName} ${name} ${eventType}`,\n type: \"info\",\n duration: 5000,\n closable: true,\n }),\n );\n }\n invalidateCaches();\n } else if (data.command === \"relaunch\") {\n setEnvStatus(\"relaunch\");\n } else {\n // Handle broadcast events\n const { id, title, description, status, duration } = data.event;\n setArtifactsUpdatedToastId(\n toaster.create({\n id: id || \"broadcast\",\n title,\n description,\n type: status ?? \"info\",\n duration: duration ?? 5000,\n closable: true,\n }),\n );\n }\n } catch (err) {\n console.error(err);\n }\n };\n ws.onerror = (err) => {\n console.error(\"An error occurred during Handling WebSockets\", err);\n };\n ws.onclose = () => {\n setStatus((status) => {\n if (status === \"connected\") {\n return \"disconnected\";\n }\n return status;\n });\n\n ref.current.ws = undefined;\n };\n }, [invalidateCaches, baseUrl, apiPrefix]);\n\n useEffect(() => {\n // Skip WebSocket connection if disabled (e.g., cloud mode)\n if (!enabled) {\n return;\n }\n\n const refObj = ref.current;\n connect();\n return () => {\n if (refObj.ws) {\n refObj.ws.close();\n }\n };\n }, [connect, enabled]);\n\n return {\n connectionStatus: status,\n connect,\n envStatus: envStatus,\n };\n}\n\ninterface LineageGraphAdapterProps {\n children: React.ReactNode;\n}\n\n/**\n * LineageGraphAdapter - Bridges OSS data fetching with @datarecce/ui's LineageGraphProvider\n *\n * This adapter:\n * 1. Does data fetching (useQuery for lineage data)\n * 2. Handles WebSocket connection for real-time updates\n * 3. Renders disconnect/relaunch modals\n * 4. Wraps @datarecce/ui's LineageGraphProvider, passing fetched data as props\n *\n * The separation allows @datarecce/ui to be reusable (props-driven, no fetching)\n * while OSS app handles its own data fetching needs.\n */\nexport function LineageGraphAdapter({ children }: LineageGraphAdapterProps) {\n const {\n idleTimeout,\n remainingSeconds,\n isEnabled,\n setDisconnected,\n resetConnection,\n } = useIdleTimeout();\n\n // Get configured API client from context\n const { apiClient, apiPrefix, baseUrl } = useApiConfig();\n\n const queryServerInfo = useQuery({\n queryKey: cacheKeys.lineage(),\n queryFn: () => getServerInfo(apiClient),\n });\n\n const queryRunAggregated = useQuery({\n queryKey: cacheKeys.runsAggregated(),\n queryFn: () => aggregateRuns(apiClient),\n });\n\n const lineageGraph = useMemo(() => {\n const lineage = queryServerInfo.data?.lineage;\n if (!lineage?.base) {\n return undefined;\n }\n\n return buildLineageGraph(lineage.base, lineage.current, lineage.diff);\n }, [queryServerInfo.data]);\n\n const errorMessage = queryServerInfo.error?.message;\n const {\n state_metadata: stateMetadata,\n lineage,\n sqlmesh,\n demo: isDemoSite,\n codespace: isCodespace,\n review_mode: reviewMode,\n cloud_mode: cloudMode,\n file_mode: fileMode,\n filename: fileName,\n adapter_type: adapterType,\n git,\n pull_request: pullRequest,\n support_tasks: supportTasks,\n } = queryServerInfo.data ?? {\n demo: false,\n };\n\n const dbtBase = lineage?.base.manifest_metadata;\n const dbtCurrent = lineage?.current.manifest_metadata;\n\n const envInfo: EnvInfo = {\n stateMetadata,\n adapterType,\n git,\n pullRequest,\n dbt: {\n base: dbtBase,\n current: dbtCurrent,\n },\n sqlmesh,\n };\n\n // Pass apiPrefix and baseUrl to useLineageWatcher for WebSocket connection\n const { connectionStatus, connect, envStatus } = useLineageWatcher({\n enabled: true,\n baseUrl,\n apiPrefix,\n });\n\n // Handle connection status changes for idle timeout\n useEffect(() => {\n if (connectionStatus === \"disconnected\") {\n // Stop countdown and keep-alive when disconnected\n setDisconnected();\n } else if (connectionStatus === \"connected\") {\n // Reset countdown when reconnected (e.g., after Retry)\n resetConnection();\n }\n }, [connectionStatus, setDisconnected, resetConnection]);\n\n const { data: flags, isLoading } = useRecceServerFlag();\n const { featureToggles, shareUrl } = useRecceInstanceContext();\n const [relaunchHintOpen, setRelaunchHintOpen] = useState<boolean>(false);\n const [prevRelaunchCondition, setPrevRelaunchCondition] =\n useState<boolean>(false);\n const queryClient = useQueryClient();\n\n // Calculate if modal should be open (during render)\n const shouldShowRelaunch =\n !isLoading &&\n envStatus === \"relaunch\" &&\n flags?.single_env_onboarding === true &&\n flags.show_relaunch_hint;\n\n // Adjust state during render when condition changes\n if (shouldShowRelaunch !== prevRelaunchCondition) {\n setPrevRelaunchCondition(shouldShowRelaunch);\n setRelaunchHintOpen(shouldShowRelaunch);\n }\n\n // Track side effect only when modal opens (remains in effect)\n useEffect(() => {\n if (shouldShowRelaunch && relaunchHintOpen) {\n trackSingleEnvironment({ action: \"target_base_added\" });\n }\n }, [shouldShowRelaunch, relaunchHintOpen]);\n\n const handleRelaunchClose = () => {\n setRelaunchHintOpen(false);\n void markRelaunchHintCompleted(apiClient);\n void queryClient.invalidateQueries({ queryKey: cacheKeys.flag() });\n };\n\n // Callback handlers for the provider\n const handleRefetchLineageGraph = useCallback(() => {\n void queryRunAggregated.refetch();\n }, [queryRunAggregated]);\n\n const handleRefetchRunsAggregated = useCallback(() => {\n void queryRunAggregated.refetch();\n }, [queryRunAggregated]);\n\n return (\n <>\n <LineageGraphProvider\n lineageGraph={lineageGraph}\n envInfo={envInfo}\n reviewMode={reviewMode}\n cloudMode={cloudMode}\n fileMode={fileMode}\n fileName={fileName}\n isDemoSite={isDemoSite ?? false}\n isCodespace={isCodespace}\n isLoading={queryServerInfo.isLoading}\n error={errorMessage}\n supportTasks={supportTasks}\n onRefetchLineageGraph={handleRefetchLineageGraph}\n runsAggregated={queryRunAggregated.data}\n onRefetchRunsAggregated={handleRefetchRunsAggregated}\n >\n {children}\n </LineageGraphProvider>\n\n <MuiDialog\n open={connectionStatus === \"disconnected\"}\n onClose={() => void 0}\n >\n {shareUrl && featureToggles.mode !== null ? (\n <RecceInstanceDisconnectedModalContent\n shareUrl={shareUrl}\n mode={featureToggles.mode}\n supportCalendarUrl={RECCE_SUPPORT_CALENDAR_URL}\n LinkComponent={({\n href,\n children,\n }: {\n href: string;\n children: ReactNode;\n }) => {\n return (\n <NextLink href={href} passHref>\n {children}\n </NextLink>\n );\n }}\n />\n ) : (\n <ServerDisconnectedModalContent\n connect={connect}\n idleSeconds={\n // Only show idle time if disconnected due to idle timeout\n // (idle timeout enabled AND remaining time was near zero)\n isEnabled &&\n idleTimeout !== null &&\n remainingSeconds !== null &&\n remainingSeconds <= 5\n ? idleTimeout - Math.max(0, remainingSeconds)\n : undefined\n }\n />\n )}\n </MuiDialog>\n\n {flags?.single_env_onboarding && (\n <MuiDialog open={relaunchHintOpen} onClose={handleRelaunchClose}>\n <DialogTitle>Target-base Added</DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={handleRelaunchClose}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n <DialogContent>\n <Typography>Please restart the Recce server.</Typography>\n </DialogContent>\n <DialogActions>\n <Button\n color=\"iochmara\"\n variant=\"contained\"\n onClick={handleRelaunchClose}\n >\n Got it!\n </Button>\n </DialogActions>\n </MuiDialog>\n )}\n </>\n );\n}\n\n// Note: useLineageGraphContext and useRunsAggregated are now imported directly from @datarecce/ui/contexts\n// This adapter only exports LineageGraphAdapter component and OSS-specific types (EnvInfo)\n","\"use client\";\n\nimport { type ReactNode, useState } from \"react\";\nimport { QueryProvider, useQueryContext } from \"../providers\";\n\ninterface QueryContextAdapterProps {\n children: ReactNode;\n}\n\nexport const defaultSqlQuery = 'select * from {{ ref(\"mymodel\") }}';\n\n/**\n * OSS-compatible QueryContext type with required fields.\n * The @datarecce/ui QueryContextType has optional OSS fields,\n * but OSS components expect them to be defined.\n */\nexport interface OSSQueryContext {\n sqlQuery: string;\n setSqlQuery: (sql: string) => void;\n primaryKeys: string[] | undefined;\n setPrimaryKeys: (pks: string[] | undefined) => void;\n isCustomQueries: boolean;\n setCustomQueries: (isCustom: boolean) => void;\n baseSqlQuery: string;\n setBaseSqlQuery: (sql: string) => void;\n}\n\n/**\n * QueryContextAdapter bridges OSS with @datarecce/ui's QueryProvider.\n *\n * Unlike CheckContextAdapter, this adapter manages internal state because\n * OSS's RecceQueryContext manages input state (sqlQuery, primaryKeys, etc.)\n * via useState, while @datarecce/ui's QueryProvider is props-driven.\n *\n * This adapter:\n * 1. Creates internal state matching OSS's interface\n * 2. Passes state and setters to QueryProvider as props\n * 3. Allows consumers to use the same hooks as before\n */\nexport function QueryContextAdapter({ children }: QueryContextAdapterProps) {\n // OSS input state\n const [sqlQuery, setSqlQuery] = useState<string>(defaultSqlQuery);\n const [baseSqlQuery, setBaseSqlQuery] = useState<string>(defaultSqlQuery);\n const [isCustomQueries, setCustomQueries] = useState<boolean>(false);\n const [primaryKeys, setPrimaryKeys] = useState<string[] | undefined>();\n\n return (\n <QueryProvider\n // Pass state as props to @datarecce/ui QueryProvider\n sql={sqlQuery}\n sqlQuery={sqlQuery}\n setSqlQuery={setSqlQuery}\n primaryKeys={primaryKeys}\n setPrimaryKeys={setPrimaryKeys}\n isCustomQueries={isCustomQueries}\n setCustomQueries={setCustomQueries}\n baseSqlQuery={baseSqlQuery}\n setBaseSqlQuery={setBaseSqlQuery}\n >\n {children}\n </QueryProvider>\n );\n}\n\n// Note: QueryContextType, QueryProviderProps, QueryResult are now imported directly from @datarecce/ui/providers\n// This adapter only exports QueryContextAdapter, useRecceQueryContext, defaultSqlQuery, and OSSQueryContext type\n\n// No-op fallbacks for when hook is used outside provider\nconst noopSetSqlQuery = (_sql: string) => {\n // Intentionally empty - fallback when used outside QueryContextAdapter\n};\nconst noopSetPrimaryKeys = (_pks: string[] | undefined) => {\n // Intentionally empty - fallback when used outside QueryContextAdapter\n};\nconst noopSetCustomQueries = (_isCustom: boolean) => {\n // Intentionally empty - fallback when used outside QueryContextAdapter\n};\nconst noopSetBaseSqlQuery = (_sql: string) => {\n // Intentionally empty - fallback when used outside QueryContextAdapter\n};\n\n/**\n * OSS-compatible hook that returns the query context with guaranteed non-optional fields.\n * This wraps @datarecce/ui's useQueryContext and provides type safety for OSS components.\n */\nexport function useRecceQueryContext(): OSSQueryContext {\n const ctx = useQueryContext();\n\n // Return OSS-compatible interface with guaranteed values\n // The QueryContextAdapter ensures these are always set\n return {\n sqlQuery: ctx.sqlQuery ?? defaultSqlQuery,\n setSqlQuery: ctx.setSqlQuery ?? noopSetSqlQuery,\n primaryKeys: ctx.primaryKeys,\n setPrimaryKeys: ctx.setPrimaryKeys ?? noopSetPrimaryKeys,\n isCustomQueries: ctx.isCustomQueries ?? false,\n setCustomQueries: ctx.setCustomQueries ?? noopSetCustomQueries,\n baseSqlQuery: ctx.baseSqlQuery ?? defaultSqlQuery,\n setBaseSqlQuery: ctx.setBaseSqlQuery ?? noopSetBaseSqlQuery,\n };\n}\n\n// Note: useQueryContext is now imported directly from @datarecce/ui/providers\n","\"use client\";\n\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { usePathname, useRouter } from \"next/navigation\";\nimport React, {\n type ComponentType,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport type { Run, RunParamTypes, RunType } from \"../api\";\nimport {\n cacheKeys,\n type SubmitRunTrackProps,\n searchRuns,\n submitRun,\n} from \"../api\";\nimport {\n findByRunType,\n type RegistryEntry,\n type RunFormParamTypes,\n type RunFormProps,\n RunModalOss,\n} from \"../components/run\";\nimport { toaster } from \"../components/ui/Toaster\";\nimport {\n type AxiosQueryParams,\n RecceActionProvider,\n type RecceActionOptions as UIRecceActionOptions,\n useRouteConfig,\n useRecceActionContext as useUIRecceActionContext,\n} from \"../contexts\";\nimport { useApiConfig } from \"./useApiConfig\";\n\n// Note: AxiosQueryParams is now imported directly from @datarecce/ui/contexts\n// This adapter only exports RecceActionAdapter component and RecceActionOptions type\n\n/**\n * Extended options for OSS that include registry-specific features\n */\nexport interface RecceActionOptions {\n showForm: boolean;\n showLast?: boolean;\n trackProps?: SubmitRunTrackProps;\n}\n\n/**\n * Internal state for managing the RunModal\n */\ninterface RunActionInternal {\n session: string;\n title: string;\n type: RunType;\n params?: RunFormParamTypes;\n lastRun?: Run;\n options?: RecceActionOptions;\n RunForm?: ComponentType<RunFormProps<RunFormParamTypes>>;\n}\n\n/**\n * Custom hook to close modal on location changes\n */\nfunction useCloseModalEffect(onClose: () => void) {\n const pathname = usePathname();\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: Specifically run on location changes\n useEffect(() => {\n onClose();\n }, [onClose, pathname]);\n}\n\ninterface RecceActionAdapterProps {\n children: React.ReactNode;\n}\n\n/**\n * RecceActionAdapter - Bridges OSS action handling with @datarecce/ui's RecceActionProvider\n *\n * This adapter:\n * 1. Keeps submitRun API logic (OSS-specific)\n * 2. Keeps RunModal UI rendering (OSS-specific)\n * 3. Keeps cache invalidation via useQueryClient (OSS-specific)\n * 4. Uses findByRunType registry lookup (OSS-specific)\n * 5. Wraps @datarecce/ui's RecceActionProvider, passing callbacks as props\n *\n * The separation allows @datarecce/ui to be reusable (props-driven, no API calls)\n * while OSS app handles its own API calls, modals, and cache management.\n */\nexport function RecceActionAdapter({ children }: RecceActionAdapterProps) {\n const { apiClient } = useApiConfig();\n const [action, setAction] = useState<RunActionInternal>();\n\n // Modal state\n const [isModalOpen, setModalOpen] = useState(false);\n const onModalOpen = useCallback(() => setModalOpen(true), []);\n const onModalClose = useCallback(() => setModalOpen(false), []);\n\n const router = useRouter();\n const pathname = usePathname();\n const queryClient = useQueryClient();\n const { basePath } = useRouteConfig();\n\n // Store a ref to the showRunId function from the provider\n // This is set by the inner component after the provider mounts\n const showRunIdRef = useRef<\n ((runId: string, refreshHistory?: boolean) => void) | null\n >(null);\n\n // Close modal on location changes\n useCloseModalEffect(onModalClose);\n\n /**\n * Callback for when a run result should be shown.\n * Handles cache invalidation when refreshHistory is true.\n */\n const handleShowRunId = useCallback(\n async (runId: string, refreshHistory?: boolean) => {\n if (refreshHistory !== false) {\n await queryClient.invalidateQueries({ queryKey: cacheKeys.runs() });\n }\n },\n [queryClient],\n );\n\n /**\n * Callback for executing a run action.\n * This is the core OSS logic that the @datarecce/ui provider delegates to.\n *\n * It:\n * - Looks up the run type in the registry\n * - Either submits the run directly or shows a form modal\n * - Handles errors with toast notifications\n * - Returns the run ID for the provider to track\n */\n const handleRunAction = useCallback(\n async (\n type: string,\n params?: AxiosQueryParams,\n options?: UIRecceActionOptions,\n ): Promise<string | undefined> => {\n try {\n const session = new Date().getTime().toString();\n let lastRun: Run | undefined = undefined;\n\n // Cast to full OSS options type (includes showLast)\n const ossOptions = options as RecceActionOptions | undefined;\n\n if (ossOptions?.showLast) {\n const runs = await searchRuns(type, params, 1, apiClient);\n if (runs.length === 1) {\n lastRun = runs[0] as Run;\n }\n }\n\n const run = findByRunType(type as RunType);\n const RunResultView = run.RunResultView as\n | RegistryEntry[\"RunResultView\"]\n | undefined;\n const { title, RunForm } = run;\n\n if (RunResultView === undefined) {\n throw new Error(`Run type ${type} does not have a result view`);\n }\n\n if (RunForm === undefined || !options?.showForm) {\n // Direct submission - no form needed\n const { run_id } = await submitRun(\n type as RunType,\n params,\n {\n nowait: true,\n trackProps: ossOptions?.trackProps,\n },\n apiClient,\n );\n\n await queryClient.invalidateQueries({ queryKey: cacheKeys.runs() });\n\n // Call showRunId BEFORE navigation (matching original RecceActionContext behavior)\n // This ensures state is set before any navigation-triggered re-renders\n if (showRunIdRef.current) {\n showRunIdRef.current(run_id);\n }\n\n // Navigate to lineage base only if we're on a lineage subpath\n // (e.g., /lineage/node/test → /lineage). Skip when already on base\n // to avoid unnecessary re-renders that reset zoom and node focus.\n const lineagePath = `${basePath}/lineage`;\n if (\n pathname.startsWith(`${lineagePath}/`) &&\n pathname !== lineagePath\n ) {\n router.push(lineagePath);\n }\n\n // Return undefined since we already called showRunId via ref\n return undefined;\n }\n // Show form modal\n setAction({\n session,\n title,\n type: type as RunType,\n params,\n lastRun,\n options: ossOptions,\n RunForm,\n });\n onModalOpen();\n\n // Return undefined - the modal will handle submission\n return undefined;\n } catch (e: unknown) {\n toaster.create({\n title: \"Failed to submit a run\",\n description: e instanceof Error ? e.message : undefined,\n type: \"error\",\n duration: 5000,\n closable: true,\n });\n return undefined;\n }\n },\n [onModalOpen, queryClient, apiClient, pathname, router, basePath],\n );\n\n /**\n * Handle form execution from RunModal.\n * Submits the run and triggers showRunId via the ref.\n */\n const handleModalExecute = useCallback(\n async (type: RunType, params: RunParamTypes) => {\n try {\n onModalClose();\n const { run_id } = await submitRun(\n type,\n params,\n {\n nowait: true,\n trackProps: action?.options?.trackProps,\n },\n apiClient,\n );\n\n // Use the ref to call showRunId from the provider context\n if (showRunIdRef.current) {\n showRunIdRef.current(run_id);\n }\n } catch (e: unknown) {\n toaster.create({\n title: \"Failed to submit a run\",\n description: e instanceof Error ? e.message : undefined,\n type: \"error\",\n duration: 5000,\n closable: true,\n });\n }\n },\n [onModalClose, action?.options?.trackProps, apiClient],\n );\n\n return (\n <RecceActionProvider\n onRunAction={handleRunAction}\n onShowRunId={handleShowRunId}\n >\n {/* Inner component captures showRunId ref */}\n <RecceActionAdapterInner showRunIdRef={showRunIdRef}>\n {children}\n </RecceActionAdapterInner>\n\n {/* RunModal for form-based runs */}\n {action && (\n <RunModalOss\n key={action.session}\n isOpen={isModalOpen}\n onClose={onModalClose}\n onExecute={handleModalExecute}\n title={action.title}\n type={action.type}\n params={action.params}\n initialRun={action.lastRun}\n RunForm={\n action.options?.showForm && action.RunForm\n ? action.RunForm\n : undefined\n }\n />\n )}\n </RecceActionProvider>\n );\n}\n\n/**\n * Inner component that captures the showRunId function from context\n * and stores it in the parent's ref for use by modal execution.\n */\ninterface RecceActionAdapterInnerProps {\n children: React.ReactNode;\n showRunIdRef: React.MutableRefObject<\n ((runId: string, refreshHistory?: boolean) => void) | null\n >;\n}\n\nfunction RecceActionAdapterInner({\n children,\n showRunIdRef,\n}: RecceActionAdapterInnerProps) {\n const { showRunId } = useUIRecceActionContext();\n\n // Store the showRunId function in the ref for parent to use\n useEffect(() => {\n showRunIdRef.current = showRunId;\n return () => {\n showRunIdRef.current = null;\n };\n }, [showRunId, showRunIdRef]);\n\n return <>{children}</>;\n}\n\n// Note: useRecceActionContext is now imported directly from @datarecce/ui/contexts\n// This adapter only exports RecceActionAdapter component and OSS-specific types\n","import React, { createContext, useContext, useState } from \"react\";\nimport { shareState } from \"../api\";\nimport { useApiConfig } from \"./useApiConfig\";\n\ninterface ShareStateProps {\n shareUrl?: string;\n isLoading: boolean;\n error?: string;\n handleShareClick: () => Promise<void>;\n}\n\nconst ShareState = createContext<ShareStateProps | undefined>(undefined);\n\nexport function RecceShareStateContextProvider({\n children,\n}: {\n children: React.ReactNode;\n}) {\n const [shareUrl, setShareUrl] = useState<string>();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string>();\n const { apiClient } = useApiConfig();\n\n const handleShareClick = async () => {\n setIsLoading(true);\n setError(undefined);\n setShareUrl(undefined);\n try {\n const response = await shareState(apiClient);\n if (response.status !== \"success\") {\n setError(response.message);\n return;\n }\n setShareUrl(response.share_url);\n } catch (err) {\n setError((err as Error).message);\n } finally {\n setIsLoading(false);\n }\n };\n\n return (\n <ShareState.Provider\n value={{ shareUrl, isLoading, error, handleShareClick }}\n >\n {children}\n </ShareState.Provider>\n );\n}\n\nexport const useRecceShareStateContext = () => {\n const context = useContext(ShareState);\n if (!context) {\n throw new Error(\n \"useRecceShareStateContext must be used within a RecceShareStateContextProvider\",\n );\n }\n return context;\n};\n","import React from \"react\";\nimport { RecceInstanceInfoProvider } from \"../contexts\";\nimport { CheckContextAdapter } from \"./CheckContextAdapter\";\nimport { LineageGraphAdapter } from \"./LineageGraphAdapter\";\nimport { QueryContextAdapter } from \"./QueryContextAdapter\";\nimport { RecceActionAdapter } from \"./RecceActionAdapter\";\nimport { RecceShareStateContextProvider } from \"./RecceShareStateContext\";\n\ninterface RecceContextProps {\n children: React.ReactNode;\n}\n\n/**\n * Main context provider for Recce application.\n *\n * For custom API configuration (e.g., recce-cloud), wrap this provider\n * with ApiConfigProvider:\n *\n * ```tsx\n * <ApiConfigProvider\n * apiPrefix=\"/api/v2/sessions/abc123\"\n * authToken=\"eyJ...\"\n * >\n * <RecceContextProvider>\n * {children}\n * </RecceContextProvider>\n * </ApiConfigProvider>\n * ```\n *\n * When used without ApiConfigProvider (OSS mode), hooks will use\n * the default axios client with standard /api/* endpoints.\n */\nexport default function RecceContextProvider({ children }: RecceContextProps) {\n return (\n <RecceInstanceInfoProvider>\n <RecceShareStateContextProvider>\n <QueryContextAdapter>\n <LineageGraphAdapter>\n <RecceActionAdapter>\n <CheckContextAdapter>{children}</CheckContextAdapter>\n </RecceActionAdapter>\n </LineageGraphAdapter>\n </QueryContextAdapter>\n </RecceShareStateContextProvider>\n </RecceInstanceInfoProvider>\n );\n}\n","import Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport { format } from \"date-fns\";\nimport saveAs from \"file-saver\";\nimport { toCanvas } from \"html-to-image\";\nimport React, {\n type RefObject,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { IoClose } from \"react-icons/io5\";\nimport { PiCopy, PiInfo } from \"react-icons/pi\";\nimport type { DataGridHandle } from \"../primitives\";\nimport { colors } from \"../theme\";\nimport { useClipBoardToast } from \"./useClipBoardToast\";\n\n// Dynamic import for html2canvas-pro (externalized to consuming app)\ntype Html2CanvasFn = (\n element: HTMLElement,\n options?: Record<string, unknown>,\n) => Promise<HTMLCanvasElement>;\n\nconst loadHtml2Canvas = async (): Promise<Html2CanvasFn> => {\n // Use the package's main export - it resolves to ESM via package.json exports\n const module = await import(\"html2canvas-pro\");\n return module.default as Html2CanvasFn;\n};\n\n// Type to represent DataGridHandle which may have an element property\ntype DataGridRefType = DataGridHandle & { element?: HTMLElement };\n\n// Helper function to safely extract HTMLElement from DataGridHandle\nconst getHTMLElementFromRef = (\n refCurrent: DataGridRefType,\n): HTMLElement | undefined => {\n // DataGridHandle might have an 'element' property containing the actual HTMLElement\n if (\"element\" in refCurrent) {\n return refCurrent.element;\n }\n // Otherwise, treat the ref itself as the HTMLElement\n return refCurrent as unknown as HTMLElement;\n};\n\nexport const IGNORE_SCREENSHOT_CLASS = \"ignore-screenshot\";\n\nexport const highlightBoxShadow =\n \"rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px\";\n\nexport interface HookOptions {\n renderLibrary?: \"html2canvas\" | \"html-to-image\";\n imageType?: \"png\" | \"jpeg\";\n backgroundColor?: string | null;\n boardEffect?: boolean;\n shadowEffect?: boolean;\n borderStyle?: string;\n borderRadius?: string;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n onClipboardNotDefined?: (blob: Blob) => void;\n ignoreElements?: (element: Element) => boolean;\n}\n\nexport interface BlobHookReturn {\n status: \"idle\" | \"loading\" | \"success\" | \"error\";\n isLoading: boolean;\n isErrored: boolean;\n isSuccess: boolean;\n toImage: () => void;\n ref: RefObject<HTMLElement | null>;\n}\n\nexport function useCopyToClipboard({\n renderLibrary = \"html2canvas\",\n imageType = \"png\",\n backgroundColor = null,\n boardEffect = true,\n shadowEffect = false,\n borderStyle = `solid 1px ${colors.neutral[300]}`,\n borderRadius = \"10px\",\n onSuccess,\n onError,\n ignoreElements,\n}: HookOptions) {\n const [status, setStatus] = useState<\n \"idle\" | \"loading\" | \"success\" | \"error\"\n >(\"idle\");\n const ref = useRef<DataGridRefType>(null);\n\n // ImageDownloadModal is used for browsers that don't support ClipboardItem\n const { onOpen, setImgBlob, ImageDownloadModal } = useImageDownloadModal();\n\n const toImage = async () => {\n if (!ref.current) {\n console.error(\"No node to use for screenshot\");\n throw new Error(\"No node to use for screenshot\");\n }\n\n const nodeToUse = getHTMLElementFromRef(ref.current);\n if (!nodeToUse) {\n console.error(\"Could not get HTMLElement from ref\");\n throw new Error(\"Could not get HTMLElement from ref\");\n }\n const overflow = nodeToUse.style.overflow;\n const border = nodeToUse.style.border;\n const radius = nodeToUse.style.borderRadius;\n const background = nodeToUse.style.backgroundColor;\n const heigh = nodeToUse.style.height;\n\n function resetStyles() {\n // nodeToUse is verified non-null before resetStyles is defined\n // Capture in local const to satisfy linter\n const node = nodeToUse;\n if (node) {\n node.style.overflow = overflow;\n node.style.border = border;\n node.style.borderRadius = radius;\n node.style.backgroundColor = background;\n node.style.height = heigh;\n }\n }\n\n try {\n nodeToUse.style.overflow = \"hidden\";\n nodeToUse.style.border = boardEffect ? borderStyle : \"\";\n nodeToUse.style.borderRadius = boardEffect ? borderRadius : \"\";\n nodeToUse.style.backgroundColor = backgroundColor ?? colors.neutral[100];\n // after firefox v125, html2canvas can't get the correct style height of the element to clone\n nodeToUse.style.height = `${String(nodeToUse.offsetHeight)}px`;\n\n // Add style to make images inline-block\n // ref: https://github.com/niklasvh/html2canvas/issues/2107#issuecomment-1316354455\n const style = document.createElement(\"style\");\n document.head.appendChild(style);\n style.sheet?.insertRule(\n \"body > div:last-child img { display: inline-block; }\",\n );\n const filter = ignoreElements\n ? (n: HTMLElement) => !ignoreElements(n)\n : undefined;\n\n setStatus(\"loading\");\n let canvas: HTMLCanvasElement;\n if (renderLibrary === \"html2canvas\") {\n const html2canvas = await loadHtml2Canvas();\n canvas = await html2canvas(nodeToUse, {\n logging: false,\n backgroundColor: backgroundColor ?? colors.neutral[100],\n ignoreElements: ignoreElements,\n });\n } else {\n canvas = await toCanvas(nodeToUse, {\n filter: filter,\n }); // Use html-to-image for copy reactflow graph\n }\n\n style.remove();\n const outputCanvas = shadowEffect\n ? document.createElement(\"canvas\")\n : canvas;\n\n if (shadowEffect) {\n // Add shadow effect\n outputCanvas.width = canvas.width + 80;\n outputCanvas.height = canvas.height + 80;\n const ctx = outputCanvas.getContext(\"2d\");\n if (ctx) {\n ctx.shadowColor = \"rgba(0, 0, 0, 0.5)\";\n ctx.shadowBlur = 20;\n ctx.shadowOffsetX = 10;\n ctx.shadowOffsetY = 10;\n ctx.drawImage(canvas, 40, 40);\n } else {\n console.error(\"Error getting canvas context\");\n throw new Error(\"Error getting canvas context to add shadow effect\");\n }\n }\n\n const response = await fetch(outputCanvas.toDataURL());\n return await response.blob();\n } catch (error: unknown) {\n console.error(\"Error converting to image\", error);\n throw error;\n } finally {\n resetStyles();\n }\n };\n\n const copyToClipboard = async () => {\n try {\n await navigator.clipboard.write([\n new ClipboardItem({ [`image/${imageType}`]: toImage() }),\n ]);\n setStatus(\"success\");\n if (onSuccess) {\n onSuccess();\n }\n } catch (error) {\n if ((error as Error).message === \"ClipboardItem is not defined\") {\n const blob = await toImage();\n setImgBlob(blob);\n onOpen();\n setStatus(\"success\");\n } else {\n setStatus(\"error\");\n console.error(\"Error copying to clipboard\", error);\n if (onError) {\n onError(error);\n }\n }\n }\n };\n\n return {\n status,\n isLoading: status === \"loading\",\n isErrored: status === \"error\",\n isSuccess: status === \"success\",\n copyToClipboard,\n ImageDownloadModal,\n ref,\n };\n}\n\nexport function useCopyToClipboardButton(options?: HookOptions) {\n const { successToast, failToast } = useClipBoardToast();\n\n const { isLoading, copyToClipboard, ImageDownloadModal, ref } =\n useCopyToClipboard({\n imageType: \"png\",\n shadowEffect: true,\n backgroundColor: options?.backgroundColor ?? colors.neutral[100],\n onSuccess: () => {\n successToast(\"Copied the query result as an image to clipboard\");\n },\n onError: (error) => {\n console.error(\"Error taking screenshot\", error);\n failToast(\"Failed to copy image to clipboard\", error);\n },\n });\n\n const onMouseEnter = useCallback(() => {\n if (ref.current) {\n const nodeToUse = getHTMLElementFromRef(ref.current);\n if (nodeToUse) {\n nodeToUse.style.boxShadow = highlightBoxShadow;\n nodeToUse.style.transition = \"box-shadow 0.5s ease-in-out\";\n }\n }\n }, [ref]);\n\n const onMouseLeave = useCallback(() => {\n if (ref.current) {\n const nodeToUse = getHTMLElementFromRef(ref.current);\n if (nodeToUse) {\n nodeToUse.style.boxShadow = \"\";\n }\n }\n }, [ref]);\n\n const onCopyToClipboard = useCallback(async () => {\n if (ref.current) {\n await copyToClipboard();\n const nodeToUse = getHTMLElementFromRef(ref.current);\n if (nodeToUse) {\n nodeToUse.style.boxShadow = \"\";\n }\n } else {\n failToast(\"Failed to copy image to clipboard\", \"No content to copy\");\n }\n }, [ref, copyToClipboard, failToast]);\n\n function CopyToClipboardButton({\n imageType = \"png\",\n ...props\n }: {\n imageType?: \"png\" | \"jpeg\";\n }) {\n return (\n <>\n <Button\n size=\"small\"\n sx={{ position: \"absolute\", bottom: 16, right: 16 }}\n disabled={isLoading}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n onClick={onCopyToClipboard}\n startIcon={<PiCopy />}\n {...props}\n >\n Copy to Clipboard\n </Button>\n <ImageDownloadModal />\n </>\n );\n }\n\n return {\n ref,\n CopyToClipboardButton,\n onMouseEnter,\n onMouseLeave,\n onCopyToClipboard,\n };\n}\n\nexport function useImageDownloadModal() {\n const [open, setOpen] = useState(false);\n const [imgBlob, setImgBlob] = useState<Blob>();\n\n const onOpen = () => setOpen(true);\n const onClose = () => setOpen(false);\n\n function ImageDownloadModal() {\n const [base64Img, setBase64Img] = useState<string>();\n\n useEffect(() => {\n if (!imgBlob) {\n return;\n }\n const reader = new FileReader();\n reader.readAsDataURL(imgBlob);\n reader.onloadend = (e) => {\n if (e.target?.result != null) {\n setBase64Img(e.target.result as string);\n }\n };\n }, []);\n\n const onDownload = () => {\n if (!imgBlob) {\n return;\n }\n const now = new Date();\n const fileName = `recce-screenshot-${format(now, \"yyyy-MM-dd-HH-mm-ss\")}.png`;\n saveAs(imgBlob, fileName);\n onClose();\n };\n\n return (\n <MuiDialog open={open} onClose={onClose} maxWidth=\"sm\" fullWidth>\n <DialogTitle>Screenshot Preview</DialogTitle>\n <IconButton\n aria-label=\"close\"\n onClick={onClose}\n sx={{\n position: \"absolute\",\n right: 8,\n top: 8,\n color: \"grey.500\",\n }}\n >\n <IoClose />\n </IconButton>\n <DialogContent>\n <Stack sx={{ px: \"10px\", gap: \"10px\" }}>\n <Stack direction=\"row\" alignItems=\"center\" spacing=\"5px\">\n <Box component={PiInfo} sx={{ color: \"error.main\" }} />\n <Typography sx={{ fontWeight: 500, display: \"inline\" }}>\n Copy to the Clipboard\n </Typography>{\" \"}\n is not supported in the current browser\n </Stack>\n <Typography>Please download it directly</Typography>\n </Stack>\n <Box\n component=\"img\"\n src={base64Img}\n alt=\"screenshot\"\n sx={{ maxWidth: \"100%\" }}\n />\n </DialogContent>\n\n <DialogActions>\n <Button sx={{ mr: 1.5 }} onClick={onClose}>\n Close\n </Button>\n <Button color=\"iochmara\" variant=\"contained\" onClick={onDownload}>\n Download\n </Button>\n </DialogActions>\n </MuiDialog>\n );\n }\n\n return {\n onOpen,\n setImgBlob,\n ImageDownloadModal,\n };\n}\n","/**\n * Custom hook for managing check events (timeline/conversation).\n *\n * Provides data fetching with polling for real-time updates,\n * and mutation functions for CRUD operations.\n */\n\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n cacheKeys,\n createComment,\n deleteComment,\n listCheckEvents,\n updateComment,\n} from \"../api\";\nimport { useApiConfig } from \"./useApiConfig\";\n\nconst POLLING_INTERVAL = 10000; // 10 seconds\n\ninterface UseCheckEventsOptions {\n enabled?: boolean;\n}\n\nexport function useCheckEvents(\n checkId: string,\n options: UseCheckEventsOptions = {},\n) {\n const { enabled = true } = options;\n const queryClient = useQueryClient();\n const { apiClient } = useApiConfig();\n\n // Fetch events with polling\n const {\n data: events,\n isLoading,\n error,\n refetch,\n } = useQuery({\n queryKey: cacheKeys.checkEvents(checkId),\n queryFn: () => listCheckEvents(checkId, apiClient),\n enabled,\n refetchInterval: POLLING_INTERVAL,\n refetchIntervalInBackground: false,\n });\n\n // Create comment mutation\n const createCommentMutation = useMutation({\n mutationFn: (content: string) => createComment(checkId, content, apiClient),\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: cacheKeys.checkEvents(checkId),\n });\n },\n });\n\n // Update comment mutation\n const updateCommentMutation = useMutation({\n mutationFn: ({ eventId, content }: { eventId: string; content: string }) =>\n updateComment(checkId, eventId, content, apiClient),\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: cacheKeys.checkEvents(checkId),\n });\n },\n });\n\n // Delete comment mutation\n const deleteCommentMutation = useMutation({\n mutationFn: (eventId: string) => deleteComment(checkId, eventId, apiClient),\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: cacheKeys.checkEvents(checkId),\n });\n },\n });\n\n return {\n events: events ?? [],\n isLoading,\n error,\n refetch,\n\n // Mutations\n createComment: createCommentMutation.mutate,\n isCreatingComment: createCommentMutation.isPending,\n createCommentError: createCommentMutation.error,\n\n // Use mutateAsync for updateComment to allow awaiting in UI\n updateComment: updateCommentMutation.mutateAsync,\n isUpdatingComment: updateCommentMutation.isPending,\n updateCommentError: updateCommentMutation.error,\n\n // Use mutateAsync for deleteComment to allow awaiting in UI\n deleteComment: deleteCommentMutation.mutateAsync,\n isDeletingComment: deleteCommentMutation.isPending,\n deleteCommentError: deleteCommentMutation.error,\n };\n}\n","/**\n * @recce-migration NOT_APPLICABLE\n *\n * This hook is specific to Recce OSS and should not be migrated to @datarecce/ui.\n *\n * Reason: Server lifetime countdown is an OSS-specific feature for local\n * development servers. It shows remaining time before auto-shutdown.\n * Cloud deployments do not have this concept.\n *\n * If this changes in the future, consider:\n * - This is unlikely to be needed in cloud/shared contexts\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useTimeout } from \"usehooks-ts\";\nimport { toaster } from \"../components/ui/Toaster\";\n\nconst COUNTDOWN_CONFIG = {\n TOAST_ID: \"lifetime-countdown\",\n WARNING_THRESHOLD: 60, // seconds before expiry to show warning\n UPDATE_INTERVAL: 1000, // milliseconds\n MESSAGE: (seconds: number) =>\n `The server will be closed in ${seconds} seconds.`,\n STYLE: {\n fontFamily: \"monospace\",\n },\n} as const;\n\n/**\n * Hook to manage countdown toast notifications for server lifetime\n * @param lifetimeExpiredAt - Date when the server will expire\n */\nexport function useCountdownToast(lifetimeExpiredAt: Date | undefined) {\n const countdownToast = toaster;\n const [countdownToastId, setCountdownToastId] = useState<string | null>(null);\n const countdownIntervalRef = useRef<NodeJS.Timeout>(undefined);\n\n const calculateRemainingSeconds = useCallback(() => {\n if (!lifetimeExpiredAt) return 0;\n\n const now = new Date();\n const remaining = Math.floor(\n (lifetimeExpiredAt.getTime() - now.getTime()) / 1000,\n );\n return Math.max(0, remaining); // Ensure we don't return negative values\n }, [lifetimeExpiredAt]);\n\n const cleanupToast = useCallback(() => {\n if (countdownToastId != null) {\n countdownToast.remove(countdownToastId);\n setCountdownToastId(null);\n }\n }, [countdownToastId]);\n\n const updateToast = useCallback(() => {\n if (countdownToastId == null) return;\n\n const remainingSeconds = calculateRemainingSeconds();\n if (remainingSeconds <= 0) {\n cleanupToast();\n return;\n }\n\n countdownToast.update(countdownToastId, {\n description: COUNTDOWN_CONFIG.MESSAGE(remainingSeconds),\n });\n }, [countdownToastId, calculateRemainingSeconds, cleanupToast]);\n\n const showToast = useCallback(() => {\n if (!lifetimeExpiredAt) return;\n\n // Cleanup any existing toast before showing new one\n cleanupToast();\n\n const remainingSeconds = calculateRemainingSeconds();\n if (remainingSeconds <= 0) return;\n\n setCountdownToastId(\n countdownToast.create({\n id: COUNTDOWN_CONFIG.TOAST_ID,\n description: COUNTDOWN_CONFIG.MESSAGE(remainingSeconds),\n }),\n );\n\n countdownIntervalRef.current = setInterval(\n updateToast,\n COUNTDOWN_CONFIG.UPDATE_INTERVAL,\n );\n }, [lifetimeExpiredAt, calculateRemainingSeconds, updateToast, cleanupToast]);\n\n // Calculate delay for showing toast\n const remainingSeconds = calculateRemainingSeconds();\n const delay = lifetimeExpiredAt\n ? Math.max(0, remainingSeconds - COUNTDOWN_CONFIG.WARNING_THRESHOLD) * 1000\n : null;\n\n // Use useTimeout hook to schedule toast display\n useTimeout(showToast, delay);\n\n // Cleanup effect\n useEffect(() => {\n return cleanupToast;\n }, [cleanupToast]);\n}\n","/**\n * Hook for data export functionality (CSV, TSV, Excel)\n */\n\nimport { useCallback, useMemo } from \"react\";\nimport type { Run } from \"../api\";\nimport { toaster } from \"../components/ui/Toaster\";\nimport {\n type CSVExportOptions,\n copyToClipboard,\n downloadCSV,\n downloadExcel,\n downloadTSV,\n extractCSVData,\n generateCSVFilename,\n supportsCSVExport,\n toCSV,\n toExcelBlob,\n toTSV,\n} from \"../utils\";\n\ninterface UseCSVExportOptions {\n run?: Run;\n /** View options - displayMode is extracted if present (for query_diff views) */\n viewOptions?: Record<string, unknown>;\n}\n\ninterface UseCSVExportResult {\n /** Whether CSV export is available for this run type */\n canExportCSV: boolean;\n /** Copy result data as CSV to clipboard */\n copyAsCSV: () => Promise<void>;\n /** Copy result data as TSV to clipboard (pastes into spreadsheets) */\n copyAsTSV: () => Promise<void>;\n /** Download result data as CSV file */\n downloadAsCSV: () => void;\n /** Download result data as TSV file */\n downloadAsTSV: () => void;\n /** Download result data as Excel file */\n downloadAsExcel: () => void;\n}\n\nexport function useCSVExport({\n run,\n viewOptions,\n}: UseCSVExportOptions): UseCSVExportResult {\n const canExportCSV = useMemo(() => {\n if (!run?.type || !run?.result) return false;\n return supportsCSVExport(run.type);\n }, [run?.type, run?.result]);\n\n const getExtractedData = useCallback(() => {\n if (!run?.type || !run?.result) return null;\n\n // Extract display_mode from viewOptions if it exists (for query_diff)\n const displayMode = viewOptions?.display_mode as\n | \"inline\"\n | \"side_by_side\"\n | undefined;\n\n // Extract primary_keys from run params (for query_diff with primary keys)\n const primaryKeys = (run?.params as { primary_keys?: string[] })\n ?.primary_keys;\n\n const exportOptions: CSVExportOptions = {\n displayMode,\n primaryKeys,\n };\n\n return extractCSVData(run.type, run.result, exportOptions);\n }, [run?.type, run?.result, run?.params, viewOptions]);\n\n const getCSVContent = useCallback((): string | null => {\n const data = getExtractedData();\n if (!data) return null;\n return toCSV(data.columns, data.rows);\n }, [getExtractedData]);\n\n const getTSVContent = useCallback((): string | null => {\n const data = getExtractedData();\n if (!data) return null;\n return toTSV(data.columns, data.rows);\n }, [getExtractedData]);\n\n const copyAsCSV = useCallback(async () => {\n const content = getCSVContent();\n if (!content) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for CSV export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n await copyToClipboard(content);\n toaster.create({\n title: \"Copied to clipboard\",\n description: \"CSV data copied successfully\",\n type: \"success\",\n duration: 2000,\n });\n } catch (error) {\n console.error(\"Failed to copy CSV to clipboard:\", error);\n toaster.create({\n title: \"Copy failed\",\n description: \"Failed to copy to clipboard\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getCSVContent]);\n\n const downloadAsCSV = useCallback(() => {\n const content = getCSVContent();\n if (!content) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for CSV export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n const filename = generateCSVFilename(\n run?.type ?? \"\",\n run?.params as Record<string, unknown>,\n );\n downloadCSV(content, filename);\n toaster.create({\n title: \"Downloaded\",\n description: filename,\n type: \"success\",\n duration: 3000,\n });\n } catch (error) {\n console.error(\"Failed to download CSV file:\", error);\n toaster.create({\n title: \"Download failed\",\n description: \"Failed to download CSV file\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getCSVContent, run]);\n\n const copyAsTSV = useCallback(async () => {\n const content = getTSVContent();\n if (!content) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n await copyToClipboard(content);\n toaster.create({\n title: \"Copied to clipboard\",\n description: \"Text data copied — paste into any spreadsheet\",\n type: \"success\",\n duration: 2000,\n });\n } catch (error) {\n console.error(\"Failed to copy TSV to clipboard:\", error);\n toaster.create({\n title: \"Copy failed\",\n description: \"Failed to copy to clipboard\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getTSVContent]);\n\n const downloadAsTSV = useCallback(() => {\n const content = getTSVContent();\n if (!content) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n const filename = generateCSVFilename(\n run?.type ?? \"\",\n run?.params as Record<string, unknown>,\n ).replace(/\\.csv$/, \".tsv\");\n downloadTSV(content, filename);\n toaster.create({\n title: \"Downloaded\",\n description: filename,\n type: \"success\",\n duration: 3000,\n });\n } catch (error) {\n console.error(\"Failed to download TSV file:\", error);\n toaster.create({\n title: \"Download failed\",\n description: \"Failed to download TSV file\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getTSVContent, run]);\n\n const downloadAsExcel = useCallback(async () => {\n const data = getExtractedData();\n if (!data) {\n toaster.create({\n title: \"Export failed\",\n description: \"Unable to extract data for export\",\n type: \"error\",\n duration: 3000,\n });\n return;\n }\n\n try {\n const blob = await toExcelBlob(data.columns, data.rows);\n const filename = generateCSVFilename(\n run?.type ?? \"\",\n run?.params as Record<string, unknown>,\n ).replace(/\\.csv$/, \".xlsx\");\n downloadExcel(blob, filename);\n toaster.create({\n title: \"Downloaded\",\n description: filename,\n type: \"success\",\n duration: 3000,\n });\n } catch (error) {\n console.error(\"Failed to download Excel file:\", error);\n toaster.create({\n title: \"Download failed\",\n description: \"Failed to download Excel file\",\n type: \"error\",\n duration: 3000,\n });\n }\n }, [getExtractedData, run]);\n\n return {\n canExportCSV,\n copyAsCSV,\n copyAsTSV,\n downloadAsCSV,\n downloadAsExcel,\n downloadAsTSV,\n };\n}\n","import Box from \"@mui/material/Box\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Link from \"@mui/material/Link\";\nimport Stack from \"@mui/material/Stack\";\nimport { useState } from \"react\";\nimport { LuExternalLink } from \"react-icons/lu\";\nimport { toaster } from \"../components/ui/Toaster\";\n\nfunction ReactionFeedback({\n description,\n onLike,\n onDislike,\n onClickLink,\n externalLink,\n externalLinkText,\n}: {\n description: string;\n onLike: () => void;\n onDislike: () => void;\n onClickLink: () => void;\n externalLink?: string;\n externalLinkText?: string;\n}) {\n return (\n <Box\n sx={{\n display: \"flex\",\n gap: 4,\n justifyContent: \"center\",\n alignContent: \"center\",\n alignItems: \"center\",\n }}\n >\n {description}\n <IconButton\n aria-label=\"thumbs up\"\n onClick={onLike}\n sx={{ width: \"32px\", height: \"32px\" }}\n >\n <Box component=\"img\" src=\"/imgs/feedback/thumbs-up.png\" alt=\"like\" />\n </IconButton>\n <IconButton\n aria-label=\"thumbs down\"\n onClick={onDislike}\n sx={{ width: \"32px\", height: \"32px\" }}\n >\n <Box\n component=\"img\"\n src=\"/imgs/feedback/thumbs-down.png\"\n alt=\"dislike\"\n />\n </IconButton>\n {externalLink && externalLinkText && (\n <Link\n href={externalLink}\n target=\"_blank\"\n onClick={onClickLink}\n sx={{ textDecoration: \"underline\" }}\n >\n {externalLinkText} <LuExternalLink />\n </Link>\n )}\n </Box>\n );\n}\n\nexport function useFeedbackCollectionToast(options: {\n feedbackId: string;\n description: string;\n onFeedbackSubmit: (feedback: string) => void;\n externalLink?: string;\n externalLinkText?: string;\n}) {\n const {\n feedbackId,\n description,\n onFeedbackSubmit,\n externalLink,\n externalLinkText,\n } = options;\n const [toastId, setToastId] = useState<string | undefined>(undefined);\n\n function feedBackCollectionToast(skipBypassFeedback = false) {\n const isSkipFeedback = localStorage.getItem(feedbackId);\n if (toastId != null) {\n // Don't show the toast again if it's already active\n return;\n }\n if (isSkipFeedback === \"true\" && !skipBypassFeedback) {\n return;\n }\n\n setToastId(\n toaster.create({\n id: feedbackId,\n duration: undefined,\n type: \"success\",\n description: (\n <Stack direction=\"row\">\n <ReactionFeedback\n description={description}\n onLike={() => {\n onFeedbackSubmit(\"like\");\n toaster.dismiss(feedbackId);\n localStorage.setItem(feedbackId, \"true\");\n }}\n onDislike={() => {\n onFeedbackSubmit(\"dislike\");\n toaster.dismiss(feedbackId);\n localStorage.setItem(feedbackId, \"true\");\n }}\n externalLink={externalLink}\n externalLinkText={externalLinkText}\n onClickLink={() => {\n onFeedbackSubmit(\"link\");\n }}\n />\n </Stack>\n ),\n }),\n );\n }\n\n return {\n feedbackToast: feedBackCollectionToast,\n closeToast: () => {\n if (toastId) toaster.dismiss(toastId);\n },\n };\n}\n","/**\n * @recce-migration NOT_APPLICABLE\n *\n * This hook is specific to Recce OSS and should not be migrated to @datarecce/ui.\n *\n * Reason: Onboarding guide toasts are tied to OSS-specific feature flags and\n * user onboarding flows. Cloud has different onboarding patterns.\n *\n * If this changes in the future, consider:\n * - Creating a generic toast system in @datarecce/ui\n * - Keeping onboarding logic in host applications\n */\n\nimport { useState } from \"react\";\nimport { toaster } from \"../components/ui/Toaster\";\n\nexport function useGuideToast(options: {\n guideId: string;\n description: string;\n externalLink?: string;\n externalLinkText?: string;\n onExternalLinkClick?: () => void;\n}) {\n const [toastId, setToastId] = useState<string | undefined>(undefined);\n const { guideId, description, externalLinkText, onExternalLinkClick } =\n options;\n\n function guideToast() {\n if (toastId != null) {\n // Don't show the toast again if it's already active\n return;\n }\n\n setToastId(\n toaster.create({\n id: guideId,\n duration: 3000,\n type: \"success\",\n description: description,\n action: {\n label: externalLinkText ?? \"link\",\n onClick: () => {\n if (onExternalLinkClick) {\n onExternalLinkClick();\n }\n },\n },\n }),\n );\n }\n\n return {\n guideToast: guideToast,\n closeGuideToast: () => {\n if (toastId) toaster.dismiss(toastId);\n },\n };\n}\n","/**\n * @file useModelColumns.tsx\n * @description Hook to fetch and manage column information for a model.\n * Combines data from lineage graph context with API calls for column details.\n */\n\nimport type { AxiosInstance } from \"axios\";\nimport _ from \"lodash\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { getModelInfo, type NodeColumnData } from \"../api\";\nimport {\n type LineageGraphNode,\n useLineageGraphContext,\n} from \"../contexts/lineage\";\nimport { useApiConfigOptional } from \"../providers\";\n\n/**\n * Extract columns from a lineage graph node.\n * Combines base and current columns using union logic.\n */\nexport function extractColumns(node: LineageGraphNode): NodeColumnData[] {\n function getColumns(\n nodeData:\n | { columns?: Record<string, NodeColumnData | undefined> }\n | undefined,\n ): NodeColumnData[] {\n return nodeData?.columns\n ? Object.values(nodeData.columns).filter(\n (c): c is NodeColumnData => c != null,\n )\n : [];\n }\n\n const baseColumns = getColumns(node.data.data.base);\n const currentColumns = getColumns(node.data.data.current);\n\n return unionColumns(baseColumns, currentColumns);\n}\n\n/**\n * Create a union of base and current columns by name.\n * Columns from current take precedence if both exist.\n */\nexport function unionColumns(\n baseColumns: NodeColumnData[],\n currentColumns: NodeColumnData[],\n): NodeColumnData[] {\n const union: NodeColumnData[] = [];\n baseColumns.forEach((column) => {\n if (!union.some((c) => c.name === column.name)) {\n union.push(column);\n }\n });\n currentColumns.forEach((column) => {\n if (!union.some((c) => c.name === column.name)) {\n union.push(column);\n }\n });\n\n return union;\n}\n\n/**\n * Return type for the useModelColumns hook\n */\nexport interface UseModelColumnsReturn {\n columns: NodeColumnData[];\n primaryKey: string | undefined;\n isLoading: boolean;\n error: Error | null;\n}\n\n/**\n * Hook to fetch model column information.\n *\n * This hook combines data from the lineage graph context (if available)\n * with API calls to get detailed column information for a model.\n *\n * @param model - The model name to fetch columns for\n * @param client - Axios instance for API calls (optional - will use context if not provided)\n * @returns Object with columns, primaryKey, isLoading, and error states\n *\n * @example\n * ```tsx\n * const { columns, primaryKey, isLoading, error } = useModelColumns(modelName);\n *\n * if (isLoading) return <Loading />;\n * if (error) return <Error message={error.message} />;\n *\n * return <ColumnList columns={columns} primaryKey={primaryKey} />;\n * ```\n */\nexport function useModelColumns(\n model: string | undefined,\n client?: AxiosInstance,\n): UseModelColumnsReturn {\n const { lineageGraph } = useLineageGraphContext();\n const apiConfig = useApiConfigOptional();\n\n // Use provided client or fall back to context client\n const axiosClient = client ?? apiConfig?.apiClient;\n\n const node = _.find(lineageGraph?.nodes, {\n data: {\n name: model,\n },\n });\n\n const nodeColumns = useMemo(() => {\n return node ? extractColumns(node) : [];\n }, [node]);\n\n const [columns, setColumns] = useState<NodeColumnData[]>([]);\n const [primaryKey, setPrimaryKey] = useState<string>();\n const [isLoading, setIsLoading] = useState<boolean>(true);\n const [error, setError] = useState<Error | null>(null);\n const [prevNodeColumns, setPrevNodeColumns] = useState<NodeColumnData[]>([]);\n const [prevNodeId, setPrevNodeId] = useState(node?.id);\n\n const nodePrimaryKey = node ? node.data.data.current?.primary_key : undefined;\n\n const fetchData = useCallback(async () => {\n if (!node || !axiosClient) {\n return;\n }\n try {\n const data = await getModelInfo(node.id, axiosClient);\n const modelInfo = data.model;\n if (!modelInfo.base.columns || !modelInfo.current.columns) {\n setColumns([]);\n return;\n }\n setPrimaryKey(modelInfo.current.primary_key);\n const baseColumns = Object.values(modelInfo.base.columns);\n const currentColumns = Object.values(modelInfo.current.columns);\n setColumns(unionColumns(baseColumns, currentColumns));\n } catch (err) {\n setError(err as Error);\n }\n }, [node, axiosClient]);\n\n // Adjust state during render when node changes\n if (nodeColumns !== prevNodeColumns || node?.id !== prevNodeId) {\n setPrevNodeColumns(nodeColumns);\n setPrevNodeId(node?.id);\n\n if (nodeColumns.length > 0) {\n setColumns(nodeColumns);\n setPrimaryKey(nodePrimaryKey);\n setIsLoading(false);\n } else if (node?.id === undefined) {\n setColumns([]);\n setIsLoading(false);\n }\n // Note: fetchData case is handled separately in effect below\n }\n\n // Fetch data effect - only runs when we need to fetch\n useEffect(() => {\n if (nodeColumns.length === 0 && node?.id !== undefined) {\n fetchData().catch((e: unknown) => {\n // error is already handled in fetchData()\n console.error(e);\n });\n setIsLoading(false);\n }\n }, [fetchData, node?.id, nodeColumns]);\n\n return { columns, primaryKey, isLoading, error };\n}\n\nexport default useModelColumns;\n","import { useQuery } from \"@tanstack/react-query\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { Run } from \"../api\";\nimport { cacheKeys, cancelRun, runTypeHasRef, waitRun } from \"../api\";\nimport type { RegistryEntry } from \"../components/run\";\nimport { findByRunType } from \"../components/run\";\nimport { useRunsAggregated } from \"../contexts\";\nimport { useApiConfig } from \"./useApiConfig\";\n\nexport interface UseRunResult {\n run?: Run;\n aborting: boolean;\n isRunning: boolean;\n error: Error | null;\n onCancel: () => Promise<void>;\n RunResultView?: RegistryEntry[\"RunResultView\"] | undefined;\n}\n\nexport const useRun = (runId?: string): UseRunResult => {\n const { apiClient } = useApiConfig();\n // Initialize to true when runId is provided - a newly submitted run is typically running.\n // The first successful fetch will update this to false if the run has already completed.\n const [isRunning, setIsRunning] = useState(!!runId);\n const [aborting, setAborting] = useState(false);\n const [, refetchRunsAggregated] = useRunsAggregated();\n\n // Track the run ID that has been detected as complete to prevent re-triggering\n // This ref persists across renders and prevents the race condition where\n // React Query's fast polling (50ms) fires before state updates propagate\n const completedRunIdRef = useRef<string | null>(null);\n\n // Reset isRunning to true when runId changes (new run submitted)\n // This ensures polling starts immediately for newly submitted runs\n useEffect(() => {\n if (runId) {\n setIsRunning(true);\n // Clear the completed ref so we can detect completion for the new run\n completedRunIdRef.current = null;\n }\n }, [runId]);\n\n const { error, data: run } = useQuery({\n queryKey: cacheKeys.run(runId ?? \"\"),\n queryFn: async () => {\n // Cast from library Run to OSS Run for discriminated union support\n return (await waitRun(runId ?? \"\", isRunning ? 2 : 0, apiClient)) as Run;\n },\n enabled: !!runId,\n refetchInterval: isRunning ? 50 : false,\n retry: false,\n });\n\n // Control polling based on run completion status\n // Uses useEffect instead of state-during-render to avoid race conditions\n // with React Query's fast polling interval (50ms)\n useEffect(() => {\n if (!run) return;\n\n // Normalize status to lowercase for case-insensitive comparison\n // Backend may return \"Running\" (capitalized) or \"running\" (lowercase)\n const normalizedStatus = run.status?.toLowerCase();\n\n // Check if run has completed (has result or error)\n const isComplete = !!(error || run.result || run.error);\n const isStatusComplete = normalizedStatus && normalizedStatus !== \"running\";\n\n if (isComplete || isStatusComplete) {\n // Only trigger state update once per completed run\n if (completedRunIdRef.current !== run.run_id) {\n completedRunIdRef.current = run.run_id;\n setIsRunning(false);\n }\n } else if (normalizedStatus === \"running\") {\n // Run is still in progress\n completedRunIdRef.current = null;\n setIsRunning(true);\n }\n }, [run, error]);\n\n // Side effect: refetch aggregated runs when row count runs complete\n useEffect(() => {\n if (\n (error || run?.result || run?.error) &&\n (run?.type === \"row_count_diff\" || run?.type === \"row_count\")\n ) {\n refetchRunsAggregated?.();\n }\n }, [run, error, refetchRunsAggregated]);\n\n const onCancel = useCallback(async () => {\n setAborting(true);\n if (!runId) {\n return;\n }\n\n await cancelRun(runId, apiClient);\n return;\n }, [runId, apiClient]);\n\n let RunResultView: RegistryEntry[\"RunResultView\"] | undefined;\n if (run && runTypeHasRef(run.type)) {\n RunResultView = findByRunType(run.type)\n .RunResultView as RegistryEntry[\"RunResultView\"];\n }\n\n return {\n run,\n isRunning,\n aborting,\n error,\n onCancel,\n RunResultView,\n };\n};\n","\"use client\";\n\nimport { useTheme as useMuiTheme } from \"@mui/material/styles\";\nimport { useEffect, useState } from \"react\";\nimport { useRecceThemeOptional } from \"../providers/contexts/ThemeContext\";\nimport { colors } from \"../theme/colors\";\nimport { useIsDark } from \"./useIsDark\";\n\n/**\n * Theme-aware color utility hook\n *\n * Returns a consistent set of colors based on the current theme mode.\n *\n * **Dual-Context Support:**\n * This hook works in two contexts:\n * 1. **With RecceProvider** (recce-cloud-infra): Uses ThemeContext for theme detection\n * 2. **Without RecceProvider** (Recce OSS with next-themes): Falls back to useIsDark\n * which uses DOM class detection (.dark on <html>)\n *\n * This allows @datarecce/ui components to work in both environments without\n * requiring the host application to wrap everything in RecceProvider.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { isDark, background, text, border } = useThemeColors();\n *\n * return (\n * <Box sx={{\n * bgcolor: background.paper,\n * color: text.primary,\n * borderColor: border.default,\n * }}>\n * Content\n * </Box>\n * );\n * }\n * ```\n */\nexport function useThemeColors() {\n const muiTheme = useMuiTheme();\n // Try context first (returns null if not in RecceProvider)\n const themeContext = useRecceThemeOptional();\n // Fallback to useIsDark which has DOM class detection\n const isDarkFallback = useIsDark();\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n // Determine dark mode: prefer context if available, otherwise use fallback\n const isDark = mounted\n ? themeContext\n ? themeContext.resolvedMode === \"dark\"\n : isDarkFallback\n : false;\n\n return {\n /** Whether the current theme is dark mode */\n isDark,\n\n /** MUI theme object for direct access when needed */\n theme: muiTheme,\n\n /** Background colors */\n background: {\n /** Default page background */\n default: isDark ? colors.neutral[900] : colors.white,\n /** Paper/card background */\n paper: isDark ? colors.neutral[800] : colors.white,\n /** Subtle background for slight elevation (e.g., hover states, inputs) */\n subtle: isDark ? colors.neutral[800] : colors.neutral[50],\n /** Emphasized background for higher contrast areas */\n emphasized: isDark ? colors.neutral[700] : colors.neutral[100],\n },\n\n /** Text colors */\n text: {\n /** Primary text color */\n primary: isDark ? colors.neutral[50] : colors.neutral[900],\n /** Secondary/muted text color */\n secondary: isDark ? colors.neutral[400] : colors.neutral[600],\n /** Disabled text color */\n disabled: isDark ? colors.neutral[500] : colors.neutral[400],\n /** Inverted text (for use on dark backgrounds in light mode, etc.) */\n inverted: isDark ? colors.neutral[900] : colors.neutral[50],\n },\n\n /** Border colors */\n border: {\n /** Light border for subtle separations */\n light: isDark ? colors.neutral[700] : colors.neutral[200],\n /** Default border color */\n default: isDark ? colors.neutral[600] : colors.neutral[300],\n /** Strong border for emphasis */\n strong: isDark ? colors.neutral[500] : colors.neutral[400],\n },\n\n /** Status/semantic colors */\n status: {\n /** Added/success backgrounds */\n added: {\n bg: isDark ? colors.green[900] : colors.green[100],\n text: isDark ? colors.neutral[50] : colors.neutral[900],\n },\n /** Removed/error backgrounds */\n removed: {\n bg: isDark ? colors.red[950] : colors.red[200],\n text: isDark ? colors.neutral[50] : colors.neutral[900],\n },\n /** Modified/warning backgrounds */\n modified: {\n bg: isDark ? colors.yellow[900] : colors.amber[100],\n text: isDark ? colors.neutral[50] : colors.neutral[900],\n },\n },\n\n /** Interactive element colors */\n interactive: {\n /** Hover state background */\n hover: isDark ? colors.neutral[700] : colors.neutral[100],\n /** Active/pressed state background */\n active: isDark ? colors.neutral[600] : colors.neutral[200],\n /** Focus ring color */\n focus: colors.iochmara[500],\n },\n };\n}\n\nexport type ThemeColors = ReturnType<typeof useThemeColors>;\n","import axios, { type AxiosInstance } from \"axios\";\nimport { PUBLIC_API_URL } from \"../const\";\n\nexport interface ConnectToCloud {\n connection_url: string;\n}\n\nconst defaultApiClient = axios.create({\n baseURL: PUBLIC_API_URL,\n});\n\nexport async function connectToCloud(\n client: AxiosInstance = defaultApiClient,\n): Promise<ConnectToCloud> {\n const data = await client.post<ConnectToCloud>(\"/api/connect\");\n return data.data;\n}\n","\"use client\";\n\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport MuiDialog from \"@mui/material/Dialog\";\nimport DialogActions from \"@mui/material/DialogActions\";\nimport DialogContent from \"@mui/material/DialogContent\";\nimport DialogTitle from \"@mui/material/DialogTitle\";\nimport Link from \"@mui/material/Link\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\nimport Cookies from \"js-cookie\";\nimport { Dispatch, ReactNode, SetStateAction, useState } from \"react\";\nimport { LuExternalLink } from \"react-icons/lu\";\nimport { useRecceInstanceContext } from \"../../contexts\";\nimport { useApiConfig } from \"../../hooks\";\nimport { connectToCloud } from \"../../lib/api/connectToCloud\";\n\ntype AuthState = \"authenticating\" | \"pending\" | \"canceled\" | \"ignored\";\n\ninterface AuthModalProps {\n handleParentClose?: Dispatch<SetStateAction<boolean>>;\n parentOpen?: boolean;\n ignoreCookie?: boolean;\n variant?: \"auth\" | \"enable-share\" | \"user-profile\";\n}\n\nexport default function AuthModal({\n handleParentClose,\n parentOpen = false,\n ignoreCookie = false,\n variant = \"auth\",\n}: AuthModalProps): ReactNode {\n const { authed } = useRecceInstanceContext();\n const { apiClient } = useApiConfig();\n const [open, setOpen] = useState(parentOpen || !authed);\n\n // Cookie handling only for auth variant\n const authStateCookieValue = (Cookies.get(\"authState\") ??\n \"pending\") as AuthState;\n const [authState, setAuthState] = useState<AuthState>(\n ignoreCookie ? \"pending\" : authStateCookieValue,\n );\n\n if (authState === \"ignored\" && !ignoreCookie) {\n return null;\n }\n\n if (authed) {\n return null;\n }\n\n // Content configuration based on variant\n const contents = {\n auth: {\n title: \"Configure Cloud Token\",\n action: \"Get token and configure\",\n },\n \"enable-share\": {\n title: \"Enable Sharing with Cloud\",\n action: \"Enable sharing\",\n },\n \"user-profile\": {\n title: \"Configure Cloud Token\",\n action: \"Get token and configure\",\n },\n };\n\n const content = contents[variant];\n\n const handleClose = () => {\n setOpen(false);\n if (handleParentClose) {\n handleParentClose(false);\n }\n };\n\n return (\n <MuiDialog\n open={open}\n onClose={handleClose}\n maxWidth=\"sm\"\n fullWidth\n slotProps={{\n paper: { sx: { borderRadius: \"1rem\" } },\n }}\n >\n {authState !== \"authenticating\" && (\n <DialogTitle sx={{ textAlign: \"center\", fontSize: \"1.5rem\" }}>\n {content.title}\n </DialogTitle>\n )}\n {authState !== \"authenticating\" ? (\n <>\n <DialogContent className=\"space-y-2 font-light\">\n <Typography>\n To enable sharing, get your token from Recce Cloud and launch your\n local instance with it.\n </Typography>\n <ul className=\"list-inside list-disc\">\n <li>Share your instance with teammates via Recce Cloud.</li>\n <li>\n Your instance will be securely and freely hosted for sharing.\n </li>\n {variant === \"auth\" && (\n <li>This step is recommended but optional.</li>\n )}\n </ul>\n <Box sx={{ display: \"flex\", gap: 1 }}>\n More directions\n <Link\n underline=\"always\"\n sx={{\n color: \"primary.main\",\n \"&:focus\": { outline: \"none\" },\n }}\n href=\"https://cloud.datarecce.io/connect-to-cloud\"\n target=\"_blank\"\n >\n here <LuExternalLink style={{ display: \"inline\" }} />\n </Link>\n </Box>\n </DialogContent>\n <DialogActions sx={{ flexDirection: \"column\", gap: 1, px: 3, pb: 3 }}>\n <Button\n fullWidth\n color=\"brand\"\n variant=\"contained\"\n sx={{ borderRadius: 2, fontWeight: 500 }}\n onClick={async () => {\n setAuthState(\"authenticating\");\n const { connection_url } = await connectToCloud(apiClient);\n // Open the connection URL in a new tab\n window.open(connection_url, \"_blank\");\n }}\n >\n {content.action} <LuExternalLink style={{ marginLeft: 4 }} />\n </Button>\n <Button\n fullWidth\n color=\"neutral\"\n variant=\"text\"\n size=\"small\"\n sx={{ borderRadius: 2, fontWeight: 500 }}\n onClick={handleClose}\n >\n {variant === \"auth\" ? \"Skip\" : \"Cancel\"}\n </Button>\n {variant === \"auth\" && (\n <Button\n fullWidth\n variant=\"text\"\n size=\"small\"\n sx={{ borderRadius: 2, fontWeight: 500, color: \"text.primary\" }}\n onClick={() => {\n Cookies.set(\"authState\", \"ignored\", {\n expires: 30,\n });\n setAuthState(\"ignored\");\n handleClose();\n }}\n >\n Snooze for 30 days\n </Button>\n )}\n </DialogActions>\n </>\n ) : (\n <>\n <DialogContent className=\"space-y-2 self-center font-light\">\n <Stack spacing={2} alignItems=\"center\" sx={{ pt: \"1rem\" }}>\n <Box\n component=\"img\"\n sx={{ height: \"6rem\", objectFit: \"contain\", mx: \"auto\", mb: 1 }}\n src=\"/imgs/reload-image.svg\"\n alt=\"Reload\"\n />\n <Typography sx={{ fontSize: \"1.5rem\", fontWeight: 500 }}>\n Reload to Finish\n </Typography>\n <Typography>\n Reload to complete connection to Recce Cloud\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ px: 3, pb: 3 }}>\n <Button\n fullWidth\n color=\"brand\"\n variant=\"contained\"\n onClick={() => {\n window.location.reload();\n }}\n >\n Reload\n </Button>\n </DialogActions>\n </>\n )}\n </MuiDialog>\n );\n}\n"],"mappings":";mrLAmCA,SAAgB,GAAY,CAC1B,WACA,cACA,oBAAoB,IACD,CACnB,GAAM,CAAE,iBAAkBA,IAAU,CAapC,OAVA,MAAgB,EACD,GAAe,KACf,OACX,SAAS,gBAAgB,UAAU,IAAI,OAAO,CAE9C,SAAS,gBAAgB,UAAU,OAAO,OAAO,EAElD,CAAC,EAAa,EAAc,CAAC,CAI9B,EAACC,GAAD,CAAyB,kBAAzB,CACG,GAAqB,EAAC,GAAD,EAAe,CAAA,CACpC,EACgB,GCrBvB,MAAM,GAAe,GALoB,CACvC,OAAQ,EAAE,CACV,UAAW,GACZ,CAEmE,CACpE,GAAa,YAAc,oBAsB3B,SAAgB,GAAc,CAC5B,WACA,SAAS,EAAE,CACX,YAAY,GACZ,QACA,kBACA,gBACA,gBACA,gBACA,gBACA,kBACA,gBAEA,wBACA,4BACqB,CAErB,IAAM,EAA0B,GAAmB,EAC7C,EAAwB,GAAiB,EAEzC,EAAe,OACZ,CACL,SACA,YACA,QAEA,gBAAiB,EACjB,cAAe,EACf,gBACA,gBACA,gBACA,kBACA,gBAEA,sBAAuB,EACvB,yBAA0B,EAC3B,EACD,CACE,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACD,CACF,CAED,OACE,EAAC,GAAa,SAAd,CAAuB,MAAO,EAC3B,WACqB,CAAA,CAI5B,SAAgB,IAAoC,CAClD,OAAO,GAAW,GAAa,CCzEjC,MAAM,GAAe,GALoB,CACvC,IAAK,GACL,YAAa,GACd,CAEmE,CACpE,GAAa,YAAc,oBAyB3B,SAAgB,GAAc,CAC5B,WACA,MAAM,GACN,cAAc,GACd,QACA,aACA,gBACA,cACA,YACA,WAEA,WACA,cACA,cACA,iBACA,kBACA,mBACA,eACA,mBACqB,CACrB,IAAM,EAAe,OACZ,CACL,MACA,cACA,QACA,aACA,gBACA,cACA,YACA,WAEA,WACA,cACA,cACA,iBACA,kBACA,mBACA,eACA,kBACD,EACD,CACE,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACD,CACF,CAED,OACE,EAAC,GAAa,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"}
|