{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "etl-pipeline-dag",
  "title": "Etl Pipeline Dag",
  "type": "registry:page",
  "files": [
    {
      "path": "packages/registry-react/blocks/etl-pipeline-dag/EtlPipelineDag.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport {\n  ArrowRight,\n  Check,\n  CheckCircle2,\n  Clock,\n  Cpu,\n  CreditCard,\n  Database,\n  FileDown,\n  GitBranch,\n  Globe,\n  Layers,\n  Loader2,\n  Play,\n  ShieldCheck,\n  Workflow,\n  Zap,\n} from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Progress } from '@/components/ui/progress'\nimport { EtlNodeDetail } from './EtlNodeDetail'\n\nexport type NodeStatus = 'success' | 'running' | 'failed' | 'queued'\nexport type NodeCategory = 'ingest' | 'transform' | 'validate' | 'load' | 'model'\n\nexport interface QualityCheck {\n  id: string\n  assertion: string\n  column: string\n  status: 'passed' | 'warning' | 'failed'\n  observed: string\n  threshold: string\n}\n\nexport interface SchemaColumn {\n  name: string\n  type: string\n  nullable: boolean\n  description: string\n}\n\nexport interface LogLine {\n  timestamp: string\n  level: 'INFO' | 'WARN' | 'SQL' | 'SUCCESS' | 'ERROR'\n  source: string\n  message: string\n}\n\nexport interface DagNode {\n  id: string\n  name: string\n  category: NodeCategory\n  categoryLabel: string\n  stageNumber: number\n  stageTitle: string\n  operator: string\n  status: NodeStatus\n  duration: string\n  durationSec: number\n  records: string\n  bytes: string\n  startedAt: string\n  finishedAt: string\n  target: string\n  engine: string\n  upstream: string[]\n  downstream: string[]\n  retries: string\n  memoryPeak: string\n  cpuPeak: string\n  configParams: Record<string, string>\n  qualityChecks: QualityCheck[]\n  schemaColumns: SchemaColumn[]\n  logs: LogLine[]\n}\n\nconst pipelineNodes: DagNode[] = [\n  {\n    id: 'extract_stripe_charges',\n    name: 'extract_stripe_charges',\n    category: 'ingest',\n    categoryLabel: 'INGEST',\n    stageNumber: 1,\n    stageTitle: 'Extract & Ingest',\n    operator: 'StripeApiConnector',\n    status: 'success',\n    duration: '18s',\n    durationSec: 18.2,\n    records: '148,290 records',\n    bytes: '42.6 MB',\n    startedAt: '02:00:02 UTC',\n    finishedAt: '02:00:20 UTC',\n    target: 'raw_stripe.charges',\n    engine: 'Airbyte / Stripe REST API v2024-06',\n    upstream: [],\n    downstream: ['transform_normalize_fx'],\n    retries: '0 / 3',\n    memoryPeak: '342 MB',\n    cpuPeak: '18%',\n    configParams: {\n      endpoint: 'https://api.stripe.com/v1/charges',\n      batch_size: '10,000',\n      pagination_mode: 'starting_after cursor',\n      target_s3_bucket: 's3://lakehouse-raw/stripe/charges/dt=2026-02-21/',\n      compression_codec: 'snappy (parquet)',\n      iam_role_arn: 'arn:aws:iam::12498214:role/etl-stripe-ingestion',\n      secret_vault_ref: 'vault://production/credentials/stripe_api_key',\n    },\n    qualityChecks: [\n      {\n        id: 'q1',\n        assertion: 'HTTP 200 Response Ratio',\n        column: 'api_response',\n        status: 'passed',\n        observed: '100% (15/15 chunks)',\n        threshold: '100%',\n      },\n      {\n        id: 'q2',\n        assertion: 'Payload Schema Checksum',\n        column: 'raw_payload',\n        status: 'passed',\n        observed: 'sha256:8f4c91...',\n        threshold: 'Valid JSON',\n      },\n      {\n        id: 'q3',\n        assertion: 'Record Count Lower Bound',\n        column: 'id',\n        status: 'passed',\n        observed: '148,290 records',\n        threshold: '> 50,000',\n      },\n    ],\n    schemaColumns: [\n      {\n        name: 'id',\n        type: 'VARCHAR(64)',\n        nullable: false,\n        description: 'Stripe unique charge identifier (ch_xxx)',\n      },\n      {\n        name: 'amount',\n        type: 'BIGINT',\n        nullable: false,\n        description: 'Charge amount in smallest currency unit (cents)',\n      },\n      {\n        name: 'currency',\n        type: 'VARCHAR(3)',\n        nullable: false,\n        description: 'Three-letter ISO currency code (USD, EUR, GBP)',\n      },\n      {\n        name: 'customer_id',\n        type: 'VARCHAR(64)',\n        nullable: true,\n        description: 'Associated customer reference (cus_xxx)',\n      },\n      {\n        name: 'status',\n        type: 'VARCHAR(32)',\n        nullable: false,\n        description: 'Charge status (succeeded, pending, failed)',\n      },\n      {\n        name: 'created_at',\n        type: 'TIMESTAMP_TZ',\n        nullable: false,\n        description: 'Stripe transaction creation epoch timestamp',\n      },\n    ],\n    logs: [\n      {\n        timestamp: '02:00:02.104',\n        level: 'INFO',\n        source: 'worker_4',\n        message: 'Initializing Stripe extraction worker on task runner runner-us-east-1a',\n      },\n      {\n        timestamp: '02:00:02.482',\n        level: 'INFO',\n        source: 'auth',\n        message: 'Authenticated with KMS secret token: sec_live_stripe_vault_****',\n      },\n      {\n        timestamp: '02:00:03.119',\n        level: 'INFO',\n        source: 'query',\n        message: 'Requesting charges filter: created >= 1708473600 (2026-02-21 00:00:00 UTC)',\n      },\n      {\n        timestamp: '02:00:05.892',\n        level: 'INFO',\n        source: 'stream',\n        message: 'Chunk 1/15 fetched: 10,000 records (HTTP 200 OK - 2.4s latency)',\n      },\n      {\n        timestamp: '02:00:09.140',\n        level: 'INFO',\n        source: 'stream',\n        message: 'Chunk 5/15 fetched: 50,000 records (HTTP 200 OK - 1.8s latency)',\n      },\n      {\n        timestamp: '02:00:14.730',\n        level: 'INFO',\n        source: 'stream',\n        message: 'Chunk 11/15 fetched: 110,000 records (HTTP 200 OK - 1.9s latency)',\n      },\n      {\n        timestamp: '02:00:18.915',\n        level: 'INFO',\n        source: 'stream',\n        message: 'Chunk 15/15 fetched: 148,290 records (HTTP 200 OK - 1.1s latency)',\n      },\n      {\n        timestamp: '02:00:19.420',\n        level: 'INFO',\n        source: 'parquet',\n        message: 'Staged parquet buffer to s3://lakehouse-raw/stripe/charges/dt=2026-02-21/part-001.parquet',\n      },\n      {\n        timestamp: '02:00:20.312',\n        level: 'SUCCESS',\n        source: 'task',\n        message: 'Task extract_stripe_charges completed successfully in 18.208s. Checksum verified: sha256:8f4c91',\n      },\n    ],\n  },\n  {\n    id: 'extract_currency_rates',\n    name: 'extract_currency_rates',\n    category: 'ingest',\n    categoryLabel: 'INGEST',\n    stageNumber: 1,\n    stageTitle: 'Extract & Ingest',\n    operator: 'EcbRatesConnector',\n    status: 'success',\n    duration: '4s',\n    durationSec: 4.1,\n    records: '34 FX pairs',\n    bytes: '128 KB',\n    startedAt: '02:00:02 UTC',\n    finishedAt: '02:00:06 UTC',\n    target: 'raw_currency.rates',\n    engine: 'ECB SDMX API / FastHTTP',\n    upstream: [],\n    downstream: ['transform_normalize_fx'],\n    retries: '0 / 3',\n    memoryPeak: '84 MB',\n    cpuPeak: '6%',\n    configParams: {\n      endpoint: 'https://data-api.ecb.europa.eu/service/data/EXR/D..EUR.SP00.A',\n      benchmark_currency: 'USD',\n      feed_type: 'Daily Official Reference Spot',\n      output_format: 'JSON / Arrow Table',\n      target_s3_bucket: 's3://lakehouse-raw/rates/dt=2026-02-21/',\n    },\n    qualityChecks: [\n      {\n        id: 'q1',\n        assertion: 'Base Currency Coverage',\n        column: 'currency_code',\n        status: 'passed',\n        observed: '34/34 major currencies',\n        threshold: '>= 30',\n      },\n      {\n        id: 'q2',\n        assertion: 'EUR/USD Rate Sanity Check',\n        column: 'spot_rate',\n        status: 'passed',\n        observed: '1.0842 USD',\n        threshold: '0.80 - 1.40',\n      },\n      {\n        id: 'q3',\n        assertion: 'Zero Value Absence',\n        column: 'rate_multiplier',\n        status: 'passed',\n        observed: '0 non-zero values',\n        threshold: '0 zeros',\n      },\n    ],\n    schemaColumns: [\n      {\n        name: 'currency_code',\n        type: 'VARCHAR(3)',\n        nullable: false,\n        description: 'Three letter ISO code (EUR, GBP, JPY, CAD)',\n      },\n      {\n        name: 'rate_to_usd',\n        type: 'DECIMAL(12, 6)',\n        nullable: false,\n        description: 'Standardized multiplier to convert 1 unit to USD',\n      },\n      {\n        name: 'published_date',\n        type: 'DATE',\n        nullable: false,\n        description: 'Official publication date by Central Bank',\n      },\n      {\n        name: 'ingested_at',\n        type: 'TIMESTAMP_TZ',\n        nullable: false,\n        description: 'Ingestion pipeline timestamp',\n      },\n    ],\n    logs: [\n      {\n        timestamp: '02:00:02.108',\n        level: 'INFO',\n        source: 'worker_1',\n        message: 'Starting currency spot exchange rate ingestion from European Central Bank API',\n      },\n      {\n        timestamp: '02:00:02.740',\n        level: 'INFO',\n        source: 'http',\n        message: 'GET https://data-api.ecb.europa.eu/service/data/EXR/D..EUR.SP00.A?startPeriod=2026-02-20',\n      },\n      {\n        timestamp: '02:00:03.921',\n        level: 'INFO',\n        source: 'parser',\n        message: 'SDMX-ML XML payload decoded (34 reference base currencies parsed)',\n      },\n      {\n        timestamp: '02:00:04.450',\n        level: 'INFO',\n        source: 'transform',\n        message:\n          'Inverted base currency rates against USD benchmark (EUR/USD: 1.0842, GBP/USD: 1.2610, JPY/USD: 154.20)',\n      },\n      {\n        timestamp: '02:00:05.620',\n        level: 'INFO',\n        source: 's3',\n        message: 'Writing FX snapshots to s3://lakehouse-raw/rates/fx_daily_20260221.json',\n      },\n      {\n        timestamp: '02:00:06.210',\n        level: 'SUCCESS',\n        source: 'task',\n        message: 'Task extract_currency_rates completed in 4.102s. 34/34 currency pairs ingested.',\n      },\n    ],\n  },\n  {\n    id: 'transform_normalize_fx',\n    name: 'transform_normalize_fx',\n    category: 'transform',\n    categoryLabel: 'TRANSFORM',\n    stageNumber: 2,\n    stageTitle: 'Transform & Normalize',\n    operator: 'PySparkBatchOperator',\n    status: 'success',\n    duration: '42s',\n    durationSec: 42.6,\n    records: '148,290 rows',\n    bytes: '58.2 MB',\n    startedAt: '02:00:21 UTC',\n    finishedAt: '02:01:03 UTC',\n    target: 'stg_payments_fx',\n    engine: 'PySpark 3.5.1 / Apache Arrow 15.0',\n    upstream: ['extract_stripe_charges', 'extract_currency_rates'],\n    downstream: ['validate_schema_anomalies'],\n    retries: '0 / 3',\n    memoryPeak: '1,280 MB',\n    cpuPeak: '74%',\n    configParams: {\n      spark_driver_memory: '4g',\n      spark_executor_memory: '8g',\n      executor_instances: '4',\n      join_strategy: 'BroadcastHashJoin(stripe.currency = fx.currency_code)',\n      precision_mode: 'DECIMAL(18, 4)',\n      output_staging_path: 's3://lakehouse-stage/payments_fx/dt=2026-02-21/',\n    },\n    qualityChecks: [\n      {\n        id: 'q1',\n        assertion: 'FX Broadcast Join Match Rate',\n        column: 'currency',\n        status: 'passed',\n        observed: '100% matched (0 orphaned FX)',\n        threshold: '100%',\n      },\n      {\n        id: 'q2',\n        assertion: 'Non-negative Amount USD',\n        column: 'amount_usd',\n        status: 'passed',\n        observed: '0 negative rows',\n        threshold: '0 negative',\n      },\n      {\n        id: 'q3',\n        assertion: 'Timestamp Timezone Normalization',\n        column: 'created_at_utc',\n        status: 'passed',\n        observed: 'All UTC ISO-8601',\n        threshold: 'Valid UTC',\n      },\n    ],\n    schemaColumns: [\n      {\n        name: 'charge_id',\n        type: 'VARCHAR(64)',\n        nullable: false,\n        description: 'Normalized unique charge identifier',\n      },\n      {\n        name: 'customer_id',\n        type: 'VARCHAR(64)',\n        nullable: true,\n        description: 'Customer identifier reference',\n      },\n      {\n        name: 'original_amount',\n        type: 'DECIMAL(18,2)',\n        nullable: false,\n        description: 'Amount in transaction original currency',\n      },\n      {\n        name: 'original_currency',\n        type: 'VARCHAR(3)',\n        nullable: false,\n        description: 'Original ISO transaction currency',\n      },\n      {\n        name: 'fx_rate_applied',\n        type: 'DECIMAL(12,6)',\n        nullable: false,\n        description: 'Exchange rate multiplier to USD',\n      },\n      {\n        name: 'amount_usd',\n        type: 'DECIMAL(18,4)',\n        nullable: false,\n        description: 'Standardized gross revenue in USD',\n      },\n      {\n        name: 'net_interchange_fee_usd',\n        type: 'DECIMAL(18,4)',\n        nullable: false,\n        description: 'Estimated payment processing fees in USD',\n      },\n      {\n        name: 'created_at_utc',\n        type: 'TIMESTAMP_TZ',\n        nullable: false,\n        description: 'UTC converted timestamp',\n      },\n    ],\n    logs: [\n      {\n        timestamp: '02:00:21.050',\n        level: 'INFO',\n        source: 'spark',\n        message: 'Initialized PySpark cluster (executor_count: 4, cores_per_exec: 4, memory: 8GB)',\n      },\n      {\n        timestamp: '02:00:22.410',\n        level: 'INFO',\n        source: 'upstream',\n        message: 'Joined dependencies: extract_stripe_charges (OK), extract_currency_rates (OK)',\n      },\n      {\n        timestamp: '02:00:24.980',\n        level: 'INFO',\n        source: 'dag.plan',\n        message: 'Logical plan: BroadcastHashJoin(stripe_charges.currency = fx_rates.currency_code)',\n      },\n      {\n        timestamp: '02:00:30.120',\n        level: 'INFO',\n        source: 'spark.exec',\n        message: 'Partition 0-7 completed: Normalized cents to standardized float amounts',\n      },\n      {\n        timestamp: '02:00:37.450',\n        level: 'INFO',\n        source: 'spark.exec',\n        message: 'Calculated converted_amount_usd, net_interchange_fee_usd, and tax_withheld_usd',\n      },\n      {\n        timestamp: '02:00:48.890',\n        level: 'INFO',\n        source: 'spark.exec',\n        message: 'Partition 8-15 completed: Applied ISO-8601 UTC timestamp timezone adjustments',\n      },\n      {\n        timestamp: '02:00:59.320',\n        level: 'INFO',\n        source: 'io.write',\n        message: 'Emitted staging dataset to s3://lakehouse-stage/payments_fx/dt=2026-02-21/ (148,290 records)',\n      },\n      {\n        timestamp: '02:01:03.650',\n        level: 'SUCCESS',\n        source: 'task',\n        message: 'Task transform_normalize_fx finished in 42.600s. 0 join drops, 100% matched FX rates.',\n      },\n    ],\n  },\n  {\n    id: 'validate_schema_anomalies',\n    name: 'validate_schema_anomalies',\n    category: 'validate',\n    categoryLabel: 'VALIDATE',\n    stageNumber: 3,\n    stageTitle: 'Quality & Audit',\n    operator: 'GreatExpectationsOperator',\n    status: 'success',\n    duration: '14s',\n    durationSec: 14.3,\n    records: '18/18 checks',\n    bytes: '1.2 MB',\n    startedAt: '02:01:04 UTC',\n    finishedAt: '02:01:18 UTC',\n    target: 'anomalies_audit',\n    engine: 'Great Expectations 0.18 + Monte Carlo',\n    upstream: ['transform_normalize_fx'],\n    downstream: ['load_snowflake_warehouse'],\n    retries: '0 / 3',\n    memoryPeak: '412 MB',\n    cpuPeak: '28%',\n    configParams: {\n      suite_name: 'suite_stripe_payments_strict_v3',\n      assertion_count: '18',\n      anomaly_detection_model: 'Z-Score 3-Sigma Rolling Window (30-day baseline)',\n      halt_pipeline_on_error: 'True',\n      alerting_webhook: 'slack://#data-pipeline-alerts',\n    },\n    qualityChecks: [\n      {\n        id: 'q1',\n        assertion: 'expect_column_values_to_not_be_null',\n        column: 'charge_id',\n        status: 'passed',\n        observed: '148,290 / 148,290 (100%)',\n        threshold: '100% non-null',\n      },\n      {\n        id: 'q2',\n        assertion: 'expect_column_values_to_be_unique',\n        column: 'charge_id',\n        status: 'passed',\n        observed: '0 duplicate keys',\n        threshold: '0 duplicates',\n      },\n      {\n        id: 'q3',\n        assertion: 'expect_column_values_to_be_in_set',\n        column: 'status',\n        status: 'passed',\n        observed: 'succeeded, failed, refunded',\n        threshold: 'Allowed set',\n      },\n      {\n        id: 'q4',\n        assertion: 'expect_column_values_to_be_between',\n        column: 'amount_usd',\n        status: 'passed',\n        observed: '$0.50 - $24,900.00',\n        threshold: '$0.50 - $250k',\n      },\n      {\n        id: 'q5',\n        assertion: 'expect_table_row_count_to_be_between',\n        column: '*',\n        status: 'passed',\n        observed: '148,290 rows',\n        threshold: '100,000 - 200,000',\n      },\n      {\n        id: 'q6',\n        assertion: 'expect_volume_drift_z_score_within_bound',\n        column: 'daily_volume',\n        status: 'passed',\n        observed: 'z = +0.42 (Normal)',\n        threshold: '|z| < 3.0',\n      },\n    ],\n    schemaColumns: [\n      {\n        name: 'test_id',\n        type: 'VARCHAR(64)',\n        nullable: false,\n        description: 'Unique assertion execution ID',\n      },\n      {\n        name: 'expectation_type',\n        type: 'VARCHAR(128)',\n        nullable: false,\n        description: 'Great Expectations rule name',\n      },\n      {\n        name: 'target_column',\n        type: 'VARCHAR(64)',\n        nullable: true,\n        description: 'Target column evaluated',\n      },\n      {\n        name: 'result_status',\n        type: 'VARCHAR(16)',\n        nullable: false,\n        description: 'PASS or FAIL status',\n      },\n      {\n        name: 'observed_metrics',\n        type: 'JSON',\n        nullable: false,\n        description: 'Computed profiling metrics summary',\n      },\n    ],\n    logs: [\n      {\n        timestamp: '02:01:04.110',\n        level: 'INFO',\n        source: 'gx.suite',\n        message: 'Loading expectation suite: suite_stripe_payments_strict_v3',\n      },\n      {\n        timestamp: '02:01:05.420',\n        level: 'INFO',\n        source: 'assertion',\n        message: '[1/18] expect_column_values_to_not_be_null(column=charge_id) -> PASSED (148,290/148,290 valid)',\n      },\n      {\n        timestamp: '02:01:07.190',\n        level: 'INFO',\n        source: 'assertion',\n        message: '[2/18] expect_column_values_to_be_unique(column=charge_id) -> PASSED (0 duplicate keys)',\n      },\n      {\n        timestamp: '02:01:09.650',\n        level: 'INFO',\n        source: 'assertion',\n        message: '[5/18] expect_column_values_to_be_in_set(column=status, set=[succeeded, failed, refunded]) -> PASSED',\n      },\n      {\n        timestamp: '02:01:12.330',\n        level: 'INFO',\n        source: 'assertion',\n        message:\n          '[11/18] expect_column_values_to_be_between(column=amount_usd, min_value=0.50, max_value=250000.00) -> PASSED',\n      },\n      {\n        timestamp: '02:01:15.840',\n        level: 'INFO',\n        source: 'assertion',\n        message:\n          '[18/18] expect_table_row_count_to_be_between(min_value=100000, max_value=200000) -> PASSED (148,290 rows)',\n      },\n      {\n        timestamp: '02:01:17.200',\n        level: 'INFO',\n        source: 'anomaly',\n        message:\n          'Statistical z-score drift test: Revenue mean +1.4% vs 30d rolling window (within normal range: ±3.0σ)',\n      },\n      {\n        timestamp: '02:01:18.410',\n        level: 'SUCCESS',\n        source: 'task',\n        message: 'Quality suite passed with 100% score (18/18 expectations verified, 0 anomalies detected).',\n      },\n    ],\n  },\n  {\n    id: 'load_snowflake_warehouse',\n    name: 'load_snowflake_warehouse',\n    category: 'load',\n    categoryLabel: 'LOAD',\n    stageNumber: 4,\n    stageTitle: 'Warehouse Load',\n    operator: 'SnowflakeMergeOperator',\n    status: 'success',\n    duration: '1m 20s',\n    durationSec: 80.0,\n    records: '148,290 rows',\n    bytes: '74.8 MB',\n    startedAt: '02:01:19 UTC',\n    finishedAt: '02:02:39 UTC',\n    target: 'fct_stripe_charges',\n    engine: 'Snowflake / TRANSFORMING_XL',\n    upstream: ['validate_schema_anomalies'],\n    downstream: ['refresh_dbt_analytics_marts'],\n    retries: '0 / 3',\n    memoryPeak: '520 MB',\n    cpuPeak: '15%',\n    configParams: {\n      database: 'PROD_ANALYTICS',\n      schema: 'PAYMENTS',\n      target_table: 'ANALYTICS.PAYMENTS.FCT_STRIPE_CHARGES',\n      warehouse_name: 'TRANSFORMING_XL (4X-Large)',\n      merge_primary_key: 'charge_id',\n      clustering_keys: '(created_at_utc::date, original_currency)',\n      auto_suspend_seconds: '60',\n    },\n    qualityChecks: [\n      {\n        id: 'q1',\n        assertion: 'Snowflake Transaction ACID Commit',\n        column: 'txn_id',\n        status: 'passed',\n        observed: 'Committed #TXN-89021',\n        threshold: 'Committed',\n      },\n      {\n        id: 'q2',\n        assertion: 'Merge Mutation Parity',\n        column: 'rows_merged',\n        status: 'passed',\n        observed: '142.4k ins / 5.8k upd',\n        threshold: '148,290 total',\n      },\n      {\n        id: 'q3',\n        assertion: 'Clustering Depth Efficiency',\n        column: 'clustering_depth',\n        status: 'passed',\n        observed: 'Average depth: 1.12',\n        threshold: '< 2.0',\n      },\n    ],\n    schemaColumns: [\n      {\n        name: 'charge_id',\n        type: 'VARCHAR(64)',\n        nullable: false,\n        description: 'Primary key charge hash',\n      },\n      {\n        name: 'customer_id',\n        type: 'VARCHAR(64)',\n        nullable: true,\n        description: 'Foreign key to DIM_CUSTOMERS',\n      },\n      {\n        name: 'amount_usd',\n        type: 'NUMBER(18,4)',\n        nullable: false,\n        description: 'Standardized gross charge amount in USD',\n      },\n      {\n        name: 'fee_usd',\n        type: 'NUMBER(18,4)',\n        nullable: false,\n        description: 'Interchange and payment gateway fees',\n      },\n      {\n        name: 'net_usd',\n        type: 'NUMBER(18,4)',\n        nullable: false,\n        description: 'Net deposited funds (amount_usd - fee_usd)',\n      },\n      {\n        name: 'status',\n        type: 'VARCHAR(32)',\n        nullable: false,\n        description: 'Current transactional settlement state',\n      },\n      {\n        name: 'created_at_utc',\n        type: 'TIMESTAMP_NTZ',\n        nullable: false,\n        description: 'Event timestamp',\n      },\n      {\n        name: '_dbt_loaded_at',\n        type: 'TIMESTAMP_NTZ',\n        nullable: false,\n        description: 'Warehouse load audit timestamp',\n      },\n    ],\n    logs: [\n      {\n        timestamp: '02:01:19.040',\n        level: 'INFO',\n        source: 'snowflake',\n        message: 'Establishing TLS 1.3 session to acct_finance.snowflakecomputing.com',\n      },\n      {\n        timestamp: '02:01:21.320',\n        level: 'INFO',\n        source: 'warehouse',\n        message: 'Resumed warehouse TRANSFORMING_XL (cluster_size: 4X-Large, auto_suspend: 60s)',\n      },\n      {\n        timestamp: '02:01:24.890',\n        level: 'SQL',\n        source: 'query',\n        message:\n          'MERGE INTO ANALYTICS.PAYMENTS.FCT_STRIPE_CHARGES AS target USING @STAGE_S3_PAYMENTS AS stage ON target.charge_id = stage.charge_id WHEN MATCHED THEN UPDATE SET target.status = stage.status WHEN NOT MATCHED THEN INSERT (charge_id, customer_id, amount_usd, fee_usd, net_usd, status, created_at_utc, _dbt_loaded_at) VALUES (stage.charge_id, stage.customer_id, stage.amount_usd, stage.net_interchange_fee_usd, stage.amount_usd - stage.net_interchange_fee_usd, stage.status, stage.created_at_utc, CURRENT_TIMESTAMP())',\n      },\n      {\n        timestamp: '02:01:45.120',\n        level: 'INFO',\n        source: 'pruner',\n        message: 'Partition pruning: 42 micro-partitions scanned out of 1,280 total (96.7% pruned)',\n      },\n      {\n        timestamp: '02:02:12.780',\n        level: 'INFO',\n        source: 'stats',\n        message: 'Merge stats: 142,400 rows inserted, 5,890 existing rows updated (disputes/refund status sync)',\n      },\n      {\n        timestamp: '02:02:32.410',\n        level: 'INFO',\n        source: 'cluster',\n        message: 'Automatic clustering key (created_at_utc::date, original_currency) re-balanced in background',\n      },\n      {\n        timestamp: '02:02:38.100',\n        level: 'INFO',\n        source: 'warehouse',\n        message: 'Auto-suspend timer armed for warehouse TRANSFORMING_XL',\n      },\n      {\n        timestamp: '02:02:39.050',\n        level: 'SUCCESS',\n        source: 'task',\n        message: 'Snowflake warehouse load completed successfully in 80.010s. Target table committed.',\n      },\n    ],\n  },\n  {\n    id: 'refresh_dbt_analytics_marts',\n    name: 'refresh_dbt_analytics_marts',\n    category: 'model',\n    categoryLabel: 'MODEL',\n    stageNumber: 5,\n    stageTitle: 'Analytics Marts',\n    operator: 'DbtCloudRunOperator',\n    status: 'success',\n    duration: '1m 40s',\n    durationSec: 100.0,\n    records: '12 models',\n    bytes: '112.4 MB',\n    startedAt: '02:02:40 UTC',\n    finishedAt: '02:04:20 UTC',\n    target: 'mart_finance_revenue',\n    engine: 'dbt Core 1.8.2 / Jinja',\n    upstream: ['load_snowflake_warehouse'],\n    downstream: [],\n    retries: '0 / 3',\n    memoryPeak: '890 MB',\n    cpuPeak: '62%',\n    configParams: {\n      dbt_project: 'bi_marts',\n      target_environment: 'prod_snowflake',\n      threads: '8',\n      models_selection: 'tag:finance_daily+ tag:revenue_marts+',\n      manifest_version: 'dbt-core v1.8.2 (schema v12)',\n      downstream_webhooks: 'Looker Cache Flush, Hex App Refresh, Metabase Sync',\n    },\n    qualityChecks: [\n      {\n        id: 'q1',\n        assertion: 'dbt Schema & Custom Tests',\n        column: 'all_marts',\n        status: 'passed',\n        observed: '48/48 tests passed (0 failures)',\n        threshold: '100% pass',\n      },\n      {\n        id: 'q2',\n        assertion: 'Incremental Model Freshness',\n        column: 'fct_daily_gross_revenue',\n        status: 'passed',\n        observed: 'Watermark: 2026-02-21 02:00 UTC',\n        threshold: '< 3h',\n      },\n      {\n        id: 'q3',\n        assertion: 'BI Semantic Layer Sync',\n        column: 'Looker/Hex',\n        status: 'passed',\n        observed: '3/3 endpoints acknowledged',\n        threshold: 'All ACK',\n      },\n    ],\n    schemaColumns: [\n      {\n        name: 'report_date',\n        type: 'DATE',\n        nullable: false,\n        description: 'Reporting dimension day',\n      },\n      {\n        name: 'gross_revenue_usd',\n        type: 'DECIMAL(20,2)',\n        nullable: false,\n        description: 'Total daily revenue converted to USD',\n      },\n      {\n        name: 'net_revenue_usd',\n        type: 'DECIMAL(20,2)',\n        nullable: false,\n        description: 'Gross revenue minus fees and refunds',\n      },\n      {\n        name: 'active_paying_customers',\n        type: 'INTEGER',\n        nullable: false,\n        description: 'Distinct customer count for the day',\n      },\n      {\n        name: 'dispute_rate_pct',\n        type: 'DECIMAL(6,4)',\n        nullable: false,\n        description: 'Daily payment dispute percentage',\n      },\n      {\n        name: 'mrr_impact_usd',\n        type: 'DECIMAL(18,2)',\n        nullable: false,\n        description: 'MRR movement delta attribution',\n      },\n    ],\n    logs: [\n      {\n        timestamp: '02:02:40.090',\n        level: 'INFO',\n        source: 'dbt',\n        message: 'Found 12 models, 48 tests, 6 snapshots, 4 semantic metrics in project bi_marts',\n      },\n      {\n        timestamp: '02:02:42.510',\n        level: 'INFO',\n        source: 'dbt',\n        message: 'Concurrency: 8 threads across target database PROD_ANALYTICS',\n      },\n      {\n        timestamp: '02:02:48.330',\n        level: 'INFO',\n        source: 'dbt',\n        message: '1 of 12 START incremental model marts.fct_daily_gross_revenue ................ [RUN]',\n      },\n      {\n        timestamp: '02:02:59.880',\n        level: 'INFO',\n        source: 'dbt',\n        message: '1 of 12 OK created incremental model marts.fct_daily_gross_revenue ........... [SUCCESS 11.55s]',\n      },\n      {\n        timestamp: '02:03:00.120',\n        level: 'INFO',\n        source: 'dbt',\n        message: '2 of 12 START table model marts.dim_customer_ltv .............................. [RUN]',\n      },\n      {\n        timestamp: '02:03:18.420',\n        level: 'INFO',\n        source: 'dbt',\n        message: '2 of 12 OK created table model marts.dim_customer_ltv ......................... [SUCCESS 18.30s]',\n      },\n      {\n        timestamp: '02:03:19.050',\n        level: 'INFO',\n        source: 'dbt',\n        message: '3 of 12 START view model marts.finance_mrr_arr_summary ....................... [RUN]',\n      },\n      {\n        timestamp: '02:03:26.700',\n        level: 'INFO',\n        source: 'dbt',\n        message: '3 of 12 OK created view model marts.finance_mrr_arr_summary ................... [SUCCESS 7.65s]',\n      },\n      {\n        timestamp: '02:03:27.100',\n        level: 'INFO',\n        source: 'dbt',\n        message: '4 of 12 START incremental model marts.fct_payment_dispute_rates .............. [RUN]',\n      },\n      {\n        timestamp: '02:03:45.920',\n        level: 'INFO',\n        source: 'dbt',\n        message: '4 of 12 OK created incremental model marts.fct_payment_dispute_rates ........ [SUCCESS 18.82s]',\n      },\n      {\n        timestamp: '02:03:46.300',\n        level: 'INFO',\n        source: 'dbt',\n        message: '5 to 12 START remaining downstream aggregate semantic marts .................. [RUN]',\n      },\n      {\n        timestamp: '02:04:12.180',\n        level: 'INFO',\n        source: 'dbt',\n        message: 'Running 48 data integrity tests on rebuilt marts ............................ [PASS 48/48]',\n      },\n      {\n        timestamp: '02:04:18.500',\n        level: 'INFO',\n        source: 'dbt',\n        message: 'Generated docs manifest and catalog metadata to target/manifest.json',\n      },\n      {\n        timestamp: '02:04:20.120',\n        level: 'SUCCESS',\n        source: 'dbt',\n        message:\n          'Finished running 12 models, 48 tests in 100.03s. All marts synchronized with Looker/Hex BI semantic layer.',\n      },\n    ],\n  },\n]\n\nfunction getNodeCategoryBadgeClass(category: NodeCategory) {\n  switch (category) {\n    case 'ingest':\n      return 'border-info/30 bg-info/10 text-info'\n    case 'transform':\n      return 'border-chart-1/30 bg-chart-1/10 text-chart-1'\n    case 'validate':\n      return 'border-warning/30 bg-warning/10 text-warning'\n    case 'load':\n      return 'border-chart-2/30 bg-chart-2/10 text-chart-2'\n    case 'model':\n      return 'border-success/30 bg-success/10 text-success'\n    default:\n      return 'border-border bg-muted text-muted-foreground'\n  }\n}\n\nfunction getNodeIcon(id: string) {\n  switch (id) {\n    case 'extract_stripe_charges':\n      return CreditCard\n    case 'extract_currency_rates':\n      return Globe\n    case 'transform_normalize_fx':\n      return Cpu\n    case 'validate_schema_anomalies':\n      return ShieldCheck\n    case 'load_snowflake_warehouse':\n      return Database\n    case 'refresh_dbt_analytics_marts':\n      return Layers\n    default:\n      return Workflow\n  }\n}\n\nexport interface EtlPipelineDagProps extends React.HTMLAttributes<HTMLDivElement> {\n  initialNodeId?: string\n}\n\nexport function EtlPipelineDag({ initialNodeId = 'extract_stripe_charges', className, ...props }: EtlPipelineDagProps) {\n  const [selectedNodeId, setSelectedNodeId] = React.useState<string>(initialNodeId)\n  const [copiedAllLogs, setCopiedAllLogs] = React.useState<boolean>(false)\n  const [isTriggering, setIsTriggering] = React.useState<boolean>(false)\n  const [triggerSuccessToast, setTriggerSuccessToast] = React.useState<boolean>(false)\n\n  const selectedNode = React.useMemo(() => {\n    return pipelineNodes.find((n) => n.id === selectedNodeId) ?? pipelineNodes[0]\n  }, [selectedNodeId])\n\n  const handleTriggerDag = React.useCallback(() => {\n    if (isTriggering) return\n    setIsTriggering(true)\n    setTriggerSuccessToast(false)\n\n    setTimeout(() => {\n      setIsTriggering(false)\n      setTriggerSuccessToast(true)\n      setTimeout(() => {\n        setTriggerSuccessToast(false)\n      }, 4000)\n    }, 1600)\n  }, [isTriggering])\n\n  const handleExportFullRunLog = React.useCallback(async () => {\n    const fullLog = pipelineNodes\n      .map((node) => {\n        const header = `=== Task: ${node.name} (${node.operator}) - Duration: ${node.duration} ===`\n        const body = node.logs.map((l) => `[${l.timestamp}] [${l.level}] [${l.source}] ${l.message}`).join('\\n')\n        return `${header}\\n${body}`\n      })\n      .join('\\n\\n')\n\n    try {\n      await navigator.clipboard.writeText(fullLog)\n      setCopiedAllLogs(true)\n      setTimeout(() => {\n        setCopiedAllLogs(false)\n      }, 2500)\n    } catch {\n      // Fallback\n    }\n  }, [])\n\n  return (\n    <div data-slot=\"etl-pipeline-dag\" className={cn('w-full space-y-6', className)} {...props}>\n      {/* Top Pipeline Header Card */}\n      <Card className=\"border-border bg-card shadow-xs\">\n        <CardContent className=\"flex flex-col gap-4 p-5 sm:flex-row sm:items-center sm:justify-between\">\n          {/* Left Title and DAG Badges */}\n          <div className=\"flex min-w-0 items-start gap-3.5 sm:items-center\">\n            <div className=\"border-primary/20 bg-primary/10 text-primary flex size-11 shrink-0 items-center justify-center rounded-xl border shadow-xs\">\n              <Workflow className=\"size-5\" />\n            </div>\n\n            <div className=\"min-w-0 space-y-1.5\">\n              <div className=\"flex flex-wrap items-center gap-2\">\n                <h2 className=\"text-foreground font-mono text-base font-semibold tracking-tight break-all sm:text-lg\">\n                  stripe_payments_daily_etl\n                </h2>\n                <Badge variant=\"secondary\" className=\"font-mono text-xs\">\n                  v2.4.1\n                </Badge>\n              </div>\n\n              <div className=\"flex flex-wrap items-center gap-2\">\n                {/* DAG Schedule Badge */}\n                <Badge variant=\"outline\" className=\"gap-1.5 font-mono text-xs shadow-xs\">\n                  <Clock className=\"text-muted-foreground size-3\" />\n                  <span>0 2 * * * · Daily at 02:00 UTC</span>\n                </Badge>\n\n                {/* State Badge */}\n                <Badge variant=\"success\" className=\"gap-1.5 font-mono text-xs shadow-xs\">\n                  <CheckCircle2 className=\"size-3\" />\n                  <span>State: Success · Run ID: #run_90412</span>\n                </Badge>\n\n                {/* Live Queue Toast */}\n                {triggerSuccessToast && (\n                  <Badge variant=\"info\" className=\"animate-in fade-in-0 gap-1.5 font-mono text-xs duration-300\">\n                    <Zap className=\"size-3\" />\n                    <span>Triggered Run #run_90413 queued!</span>\n                  </Badge>\n                )}\n              </div>\n            </div>\n          </div>\n\n          {/* Right Action Buttons */}\n          <div className=\"flex flex-wrap items-center gap-2.5\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              className=\"h-8.5 gap-1.5 text-xs font-medium\"\n              disabled={copiedAllLogs}\n              onClick={handleExportFullRunLog}\n            >\n              {copiedAllLogs ? <Check className=\"size-3.5\" /> : <FileDown className=\"size-3.5\" />}\n              <span>{copiedAllLogs ? 'Run Log Copied!' : 'Export Run Log'}</span>\n            </Button>\n\n            <Button\n              variant=\"default\"\n              size=\"sm\"\n              className=\"h-8.5 gap-1.5 text-xs font-medium\"\n              disabled={isTriggering}\n              onClick={handleTriggerDag}\n            >\n              {isTriggering ? (\n                <Loader2 className=\"size-3.5 animate-spin\" />\n              ) : (\n                <Play className=\"size-3.5 fill-current\" />\n              )}\n              <span>{isTriggering ? 'Triggering DAG Run...' : 'Trigger DAG Run'}</span>\n            </Button>\n          </div>\n        </CardContent>\n      </Card>\n\n      {/* 4 Pipeline Health Metric Cards */}\n      <div className=\"grid grid-cols-1 gap-3.5 sm:grid-cols-2 lg:grid-cols-4\">\n        {/* Total Duration */}\n        <Card className=\"border-border bg-card shadow-xs\">\n          <CardContent className=\"flex flex-col justify-between p-4.5\">\n            <div className=\"flex items-center justify-between\">\n              <span className=\"text-muted-foreground text-xs font-medium\">Total Duration</span>\n              <div className=\"border-border bg-muted/60 text-muted-foreground flex size-7 items-center justify-center rounded-md border\">\n                <Clock className=\"size-3.5\" />\n              </div>\n            </div>\n            <div className=\"mt-2 space-y-1\">\n              <div className=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">04m:18s</div>\n              <div className=\"flex items-center gap-1.5 text-xs\">\n                <span className=\"text-success font-medium\">On schedule</span>\n                <span className=\"text-muted-foreground font-mono tabular-nums\">-14s vs 7d avg</span>\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n\n        {/* Tasks Completed */}\n        <Card className=\"border-border bg-card shadow-xs\">\n          <CardContent className=\"flex flex-col justify-between p-4.5\">\n            <div className=\"flex items-center justify-between\">\n              <span className=\"text-muted-foreground text-xs font-medium\">Tasks Completed</span>\n              <div className=\"border-success/30 bg-success/10 text-success flex size-7 items-center justify-center rounded-md border\">\n                <CheckCircle2 className=\"size-3.5\" />\n              </div>\n            </div>\n            <div className=\"mt-2 space-y-1.5\">\n              <div className=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">\n                8 / 8 tasks succeeded\n              </div>\n              <div className=\"space-y-1\">\n                <Progress value={100} className=\"h-1.5\" />\n                <p className=\"text-muted-foreground text-xs\">100% completion · 0 retries · 0 failed</p>\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n\n        {/* Records Ingested */}\n        <Card className=\"border-border bg-card shadow-xs\">\n          <CardContent className=\"flex flex-col justify-between p-4.5\">\n            <div className=\"flex items-center justify-between\">\n              <span className=\"text-muted-foreground text-xs font-medium\">Records Ingested</span>\n              <div className=\"border-border bg-muted/60 text-muted-foreground flex size-7 items-center justify-center rounded-md border\">\n                <Database className=\"size-3.5\" />\n              </div>\n            </div>\n            <div className=\"mt-2 space-y-1\">\n              <div className=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">\n                148,290 records\n              </div>\n              <div className=\"flex items-center gap-1.5 text-xs\">\n                <span className=\"text-success font-medium\">100% data fidelity</span>\n                <span className=\"text-muted-foreground\">0 dropped rows</span>\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n\n        {/* Data Freshness */}\n        <Card className=\"border-border bg-card shadow-xs\">\n          <CardContent className=\"flex flex-col justify-between p-4.5\">\n            <div className=\"flex items-center justify-between\">\n              <span className=\"text-muted-foreground text-xs font-medium\">Data Freshness</span>\n              <div className=\"border-border bg-muted/60 text-muted-foreground flex size-7 items-center justify-center rounded-md border\">\n                <Zap className=\"size-3.5\" />\n              </div>\n            </div>\n            <div className=\"mt-2 space-y-1\">\n              <div className=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">\n                Lag: 12m · SLA &lt; 1h\n              </div>\n              <div className=\"flex items-center gap-1.5 text-xs\">\n                <span className=\"text-success font-medium\">SLA Healthy</span>\n                <span className=\"text-muted-foreground\">Target: &lt; 60m sync</span>\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n      </div>\n\n      {/* Interactive DAG Node Pipeline Graph */}\n      <Card className=\"border-border bg-card shadow-xs\">\n        <CardHeader className=\"pb-3\">\n          <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n            <div>\n              <div className=\"flex items-center gap-2\">\n                <CardTitle className=\"text-base font-semibold\">DAG Execution Topology</CardTitle>\n                <Badge variant=\"outline\" className=\"font-mono text-xs\">\n                  6 Connected Tasks\n                </Badge>\n              </div>\n              <CardDescription className=\"text-xs\">\n                Directed acyclic graph with dependency streams. Select any node to inspect telemetry and execution logs.\n              </CardDescription>\n            </div>\n\n            {/* Quick Topology Status Info */}\n            <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n              <div className=\"border-border bg-muted/30 text-muted-foreground flex items-center gap-1.5 rounded-md border px-2 py-1 font-mono\">\n                <span className=\"bg-success inline-block size-2 rounded-full\" />\n                <span>All Upstreams Resolved</span>\n              </div>\n              <div className=\"border-border bg-muted/30 text-muted-foreground flex items-center gap-1.5 rounded-md border px-2 py-1 font-mono\">\n                <GitBranch className=\"size-3\" />\n                <span>Topology: 2 Branches &rarr; Converged</span>\n              </div>\n            </div>\n          </div>\n        </CardHeader>\n\n        <CardContent className=\"p-4 pt-1 sm:p-6\">\n          {/* DAG Canvas Box */}\n          <div className=\"border-border bg-muted/10 relative rounded-xl border p-4 sm:p-5\">\n            {/* Desktop 5-Stage Topology Pipeline Layout */}\n            <div className=\"grid grid-cols-1 gap-5 lg:grid-cols-5\">\n              {/* Stage 1: Extract / Ingest (2 Parallel Sources) */}\n              <div className=\"flex flex-col justify-between gap-3.5\">\n                <div className=\"flex items-center justify-between px-1\">\n                  <span className=\"text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase\">\n                    01 · Extract (2)\n                  </span>\n                  <Badge variant=\"outline\" className=\"text-xs\">\n                    Parallel\n                  </Badge>\n                </div>\n\n                {/* Node 1: extract_stripe_charges */}\n                <div\n                  role=\"button\"\n                  tabIndex={0}\n                  className={cn(\n                    'group focus-visible:ring-ring relative cursor-pointer rounded-xl border p-3.5 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none',\n                    selectedNodeId === 'extract_stripe_charges'\n                      ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2'\n                      : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30',\n                  )}\n                  onClick={() => setSelectedNodeId('extract_stripe_charges')}\n                  onKeyDown={(e) => {\n                    if (e.key === 'Enter' || e.key === ' ') {\n                      e.preventDefault()\n                      setSelectedNodeId('extract_stripe_charges')\n                    }\n                  }}\n                >\n                  <div className=\"flex items-start justify-between gap-2\">\n                    <div className=\"flex items-center gap-2\">\n                      <div className=\"border-info/30 bg-info/10 text-info flex size-7 items-center justify-center rounded-md border\">\n                        <CreditCard className=\"size-3.5\" />\n                      </div>\n                      <Badge className={cn('font-mono text-xs', getNodeCategoryBadgeClass('ingest'))}>INGEST</Badge>\n                    </div>\n                    <Badge variant=\"success\" className=\"gap-1 font-mono text-xs\">\n                      <Check className=\"size-3\" />\n                      18s\n                    </Badge>\n                  </div>\n\n                  <div className=\"mt-2.5\">\n                    <div className=\"text-foreground font-mono text-xs font-semibold break-all\">\n                      extract_stripe_charges\n                    </div>\n                    <div className=\"text-muted-foreground mt-0.5 font-mono text-xs\">148.3k records · 42.6 MB</div>\n                  </div>\n\n                  <div className=\"border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs\">\n                    <span className=\"text-muted-foreground truncate font-mono\">raw_stripe.charges</span>\n                    <ArrowRight className=\"text-muted-foreground group-hover:text-primary size-3 shrink-0 transition-colors\" />\n                  </div>\n                </div>\n\n                {/* Node 2: extract_currency_rates */}\n                <div\n                  role=\"button\"\n                  tabIndex={0}\n                  className={cn(\n                    'group focus-visible:ring-ring relative cursor-pointer rounded-xl border p-3.5 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none',\n                    selectedNodeId === 'extract_currency_rates'\n                      ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2'\n                      : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30',\n                  )}\n                  onClick={() => setSelectedNodeId('extract_currency_rates')}\n                  onKeyDown={(e) => {\n                    if (e.key === 'Enter' || e.key === ' ') {\n                      e.preventDefault()\n                      setSelectedNodeId('extract_currency_rates')\n                    }\n                  }}\n                >\n                  <div className=\"flex items-start justify-between gap-2\">\n                    <div className=\"flex items-center gap-2\">\n                      <div className=\"border-info/30 bg-info/10 text-info flex size-7 items-center justify-center rounded-md border\">\n                        <Globe className=\"size-3.5\" />\n                      </div>\n                      <Badge className={cn('font-mono text-xs', getNodeCategoryBadgeClass('ingest'))}>INGEST</Badge>\n                    </div>\n                    <Badge variant=\"success\" className=\"gap-1 font-mono text-xs\">\n                      <Check className=\"size-3\" />\n                      4s\n                    </Badge>\n                  </div>\n\n                  <div className=\"mt-2.5\">\n                    <div className=\"text-foreground font-mono text-xs font-semibold break-all\">\n                      extract_currency_rates\n                    </div>\n                    <div className=\"text-muted-foreground mt-0.5 font-mono text-xs\">34 FX pairs · 128 KB</div>\n                  </div>\n\n                  <div className=\"border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs\">\n                    <span className=\"text-muted-foreground truncate font-mono\">raw_currency.rates</span>\n                    <ArrowRight className=\"text-muted-foreground group-hover:text-primary size-3 shrink-0 transition-colors\" />\n                  </div>\n                </div>\n              </div>\n\n              {/* Stage 2: Transform (PySpark Normalization) */}\n              <div className=\"flex flex-col justify-between gap-3.5\">\n                <div className=\"flex items-center justify-between px-1\">\n                  <span className=\"text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase\">\n                    02 · Transform\n                  </span>\n                  <Badge variant=\"secondary\" className=\"font-mono text-xs\">\n                    Join 2&rarr;1\n                  </Badge>\n                </div>\n\n                {/* Node 3: transform_normalize_fx */}\n                <div\n                  role=\"button\"\n                  tabIndex={0}\n                  className={cn(\n                    'group focus-visible:ring-ring relative my-auto cursor-pointer rounded-xl border p-3.5 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none',\n                    selectedNodeId === 'transform_normalize_fx'\n                      ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2'\n                      : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30',\n                  )}\n                  onClick={() => setSelectedNodeId('transform_normalize_fx')}\n                  onKeyDown={(e) => {\n                    if (e.key === 'Enter' || e.key === ' ') {\n                      e.preventDefault()\n                      setSelectedNodeId('transform_normalize_fx')\n                    }\n                  }}\n                >\n                  <div className=\"flex items-start justify-between gap-2\">\n                    <div className=\"flex items-center gap-2\">\n                      <div className=\"border-chart-1/30 bg-chart-1/10 text-chart-1 flex size-7 items-center justify-center rounded-md border\">\n                        <Cpu className=\"size-3.5\" />\n                      </div>\n                      <Badge className={cn('font-mono text-xs', getNodeCategoryBadgeClass('transform'))}>\n                        TRANSFORM\n                      </Badge>\n                    </div>\n                    <Badge variant=\"success\" className=\"gap-1 font-mono text-xs\">\n                      <Check className=\"size-3\" />\n                      42s\n                    </Badge>\n                  </div>\n\n                  <div className=\"mt-2.5\">\n                    <div className=\"text-foreground font-mono text-xs font-semibold break-all\">\n                      transform_normalize_fx\n                    </div>\n                    <div className=\"text-muted-foreground mt-0.5 font-mono text-xs\">148,290 rows · PySpark 3.5</div>\n                  </div>\n\n                  <div className=\"mt-2 flex flex-wrap gap-1\">\n                    <span className=\"border-border bg-muted/60 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      2 Upstreams\n                    </span>\n                    <span className=\"border-border bg-muted/60 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      Snappy\n                    </span>\n                  </div>\n\n                  <div className=\"border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs\">\n                    <span className=\"text-muted-foreground truncate font-mono\">stg_payments_fx</span>\n                    <ArrowRight className=\"text-muted-foreground group-hover:text-primary size-3 shrink-0 transition-colors\" />\n                  </div>\n                </div>\n              </div>\n\n              {/* Stage 3: Quality & Validation (Great Expectations) */}\n              <div className=\"flex flex-col justify-between gap-3.5\">\n                <div className=\"flex items-center justify-between px-1\">\n                  <span className=\"text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase\">\n                    03 · Quality\n                  </span>\n                  <Badge variant=\"outline\" className=\"font-mono text-xs\">\n                    18 Checks\n                  </Badge>\n                </div>\n\n                {/* Node 4: validate_schema_anomalies */}\n                <div\n                  role=\"button\"\n                  tabIndex={0}\n                  className={cn(\n                    'group focus-visible:ring-ring relative my-auto cursor-pointer rounded-xl border p-3.5 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none',\n                    selectedNodeId === 'validate_schema_anomalies'\n                      ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2'\n                      : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30',\n                  )}\n                  onClick={() => setSelectedNodeId('validate_schema_anomalies')}\n                  onKeyDown={(e) => {\n                    if (e.key === 'Enter' || e.key === ' ') {\n                      e.preventDefault()\n                      setSelectedNodeId('validate_schema_anomalies')\n                    }\n                  }}\n                >\n                  <div className=\"flex items-start justify-between gap-2\">\n                    <div className=\"flex items-center gap-2\">\n                      <div className=\"border-warning/30 bg-warning/10 text-warning flex size-7 items-center justify-center rounded-md border\">\n                        <ShieldCheck className=\"size-3.5\" />\n                      </div>\n                      <Badge className={cn('font-mono text-xs', getNodeCategoryBadgeClass('validate'))}>VALIDATE</Badge>\n                    </div>\n                    <Badge variant=\"success\" className=\"gap-1 font-mono text-xs\">\n                      <Check className=\"size-3\" />\n                      14s\n                    </Badge>\n                  </div>\n\n                  <div className=\"mt-2.5\">\n                    <div className=\"text-foreground font-mono text-xs font-semibold break-all\">\n                      validate_schema_anomalies\n                    </div>\n                    <div className=\"text-muted-foreground mt-0.5 font-mono text-xs\">18/18 checks passed (100%)</div>\n                  </div>\n\n                  <div className=\"mt-2 flex flex-wrap gap-1\">\n                    <span className=\"border-success/30 bg-success/10 text-success rounded px-1.5 py-0.5 font-mono text-xs\">\n                      0 Anomalies\n                    </span>\n                    <span className=\"border-border bg-muted/60 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      Z-Score 3σ\n                    </span>\n                  </div>\n\n                  <div className=\"border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs\">\n                    <span className=\"text-muted-foreground truncate font-mono\">anomalies_audit</span>\n                    <ArrowRight className=\"text-muted-foreground group-hover:text-primary size-3 shrink-0 transition-colors\" />\n                  </div>\n                </div>\n              </div>\n\n              {/* Stage 4: Warehouse Load (Snowflake) */}\n              <div className=\"flex flex-col justify-between gap-3.5\">\n                <div className=\"flex items-center justify-between px-1\">\n                  <span className=\"text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase\">\n                    04 · Load\n                  </span>\n                  <Badge variant=\"outline\" className=\"font-mono text-xs\">\n                    Snowflake\n                  </Badge>\n                </div>\n\n                {/* Node 5: load_snowflake_warehouse */}\n                <div\n                  role=\"button\"\n                  tabIndex={0}\n                  className={cn(\n                    'group focus-visible:ring-ring relative my-auto cursor-pointer rounded-xl border p-3.5 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none',\n                    selectedNodeId === 'load_snowflake_warehouse'\n                      ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2'\n                      : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30',\n                  )}\n                  onClick={() => setSelectedNodeId('load_snowflake_warehouse')}\n                  onKeyDown={(e) => {\n                    if (e.key === 'Enter' || e.key === ' ') {\n                      e.preventDefault()\n                      setSelectedNodeId('load_snowflake_warehouse')\n                    }\n                  }}\n                >\n                  <div className=\"flex items-start justify-between gap-2\">\n                    <div className=\"flex items-center gap-2\">\n                      <div className=\"flex size-7 items-center justify-center rounded-md border border-cyan-500/30 bg-cyan-500/10 text-cyan-500\">\n                        <Database className=\"size-3.5\" />\n                      </div>\n                      <Badge className={cn('font-mono text-xs', getNodeCategoryBadgeClass('load'))}>LOAD</Badge>\n                    </div>\n                    <Badge variant=\"success\" className=\"gap-1 font-mono text-xs\">\n                      <Check className=\"size-3\" />\n                      1m 20s\n                    </Badge>\n                  </div>\n\n                  <div className=\"mt-2.5\">\n                    <div className=\"text-foreground font-mono text-xs font-semibold break-all\">\n                      load_snowflake_warehouse\n                    </div>\n                    <div className=\"text-muted-foreground mt-0.5 font-mono text-xs\">148,290 rows merged</div>\n                  </div>\n\n                  <div className=\"mt-2 flex flex-wrap gap-1\">\n                    <span className=\"border-border bg-muted/60 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      4X-Large\n                    </span>\n                    <span className=\"border-border bg-muted/60 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      Clustered\n                    </span>\n                  </div>\n\n                  <div className=\"border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs\">\n                    <span className=\"text-muted-foreground truncate font-mono\">fct_stripe_charges</span>\n                    <ArrowRight className=\"text-muted-foreground group-hover:text-primary size-3 shrink-0 transition-colors\" />\n                  </div>\n                </div>\n              </div>\n\n              {/* Stage 5: Analytics Modeling (dbt Core) */}\n              <div className=\"flex flex-col justify-between gap-3.5\">\n                <div className=\"flex items-center justify-between px-1\">\n                  <span className=\"text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase\">\n                    05 · Marts\n                  </span>\n                  <Badge variant=\"outline\" className=\"font-mono text-xs\">\n                    dbt Core\n                  </Badge>\n                </div>\n\n                {/* Node 6: refresh_dbt_analytics_marts */}\n                <div\n                  role=\"button\"\n                  tabIndex={0}\n                  className={cn(\n                    'group focus-visible:ring-ring relative my-auto cursor-pointer rounded-xl border p-3.5 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none',\n                    selectedNodeId === 'refresh_dbt_analytics_marts'\n                      ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2'\n                      : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30',\n                  )}\n                  onClick={() => setSelectedNodeId('refresh_dbt_analytics_marts')}\n                  onKeyDown={(e) => {\n                    if (e.key === 'Enter' || e.key === ' ') {\n                      e.preventDefault()\n                      setSelectedNodeId('refresh_dbt_analytics_marts')\n                    }\n                  }}\n                >\n                  <div className=\"flex items-start justify-between gap-2\">\n                    <div className=\"flex items-center gap-2\">\n                      <div className=\"border-success/30 bg-success/10 text-success flex size-7 items-center justify-center rounded-md border\">\n                        <Layers className=\"size-3.5\" />\n                      </div>\n                      <Badge className={cn('font-mono text-xs', getNodeCategoryBadgeClass('model'))}>MODEL</Badge>\n                    </div>\n                    <Badge variant=\"success\" className=\"gap-1 font-mono text-xs\">\n                      <Check className=\"size-3\" />\n                      1m 40s\n                    </Badge>\n                  </div>\n\n                  <div className=\"mt-2.5\">\n                    <div className=\"text-foreground font-mono text-xs font-semibold break-all\">\n                      refresh_dbt_analytics_marts\n                    </div>\n                    <div className=\"text-muted-foreground mt-0.5 font-mono text-xs\">12 models · 48 tests</div>\n                  </div>\n\n                  <div className=\"mt-2 flex flex-wrap gap-1\">\n                    <span className=\"border-border bg-muted/60 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      8 Threads\n                    </span>\n                    <span className=\"border-success/30 bg-success/10 text-success rounded px-1.5 py-0.5 font-mono text-xs\">\n                      BI Synced\n                    </span>\n                  </div>\n\n                  <div className=\"border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs\">\n                    <span className=\"text-muted-foreground truncate font-mono\">mart_finance_revenue</span>\n                    <CheckCircle2 className=\"text-success size-3 shrink-0\" />\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n\n      {/* Selected Node Log Inspector Panel */}\n      <EtlNodeDetail\n        node={selectedNode}\n        categoryBadgeClass={cn('font-mono text-xs', getNodeCategoryBadgeClass(selectedNode.category))}\n        icon={getNodeIcon(selectedNode.id)}\n        onSelectNode={setSelectedNodeId}\n      />\n    </div>\n  )\n}\n\nexport default EtlPipelineDag\n",
      "type": "registry:page",
      "target": "~/components/blocks/EtlPipelineDag.tsx"
    },
    {
      "path": "packages/registry-react/blocks/etl-pipeline-dag/EtlNodeDetail.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport {\n  Check,\n  Code2,\n  Copy,\n  CornerDownRight,\n  GitBranch,\n  GitCommit,\n  Search,\n  ShieldCheck,\n  Terminal,\n  X,\n} from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardHeader } from '@/components/ui/card'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'\nimport type { DagNode } from './EtlPipelineDag'\n\nexport interface EtlNodeDetailProps {\n  node: DagNode\n  categoryBadgeClass: string\n  icon: React.ComponentType<{ className?: string }>\n  onSelectNode: (id: string) => void\n}\n\nexport function EtlNodeDetail({ node, categoryBadgeClass, icon: NodeIcon, onSelectNode }: EtlNodeDetailProps) {\n  const [activeTab, setActiveTab] = React.useState<string>('logs')\n  const [logSearchQuery, setLogSearchQuery] = React.useState<string>('')\n  const [selectedLogLevel, setSelectedLogLevel] = React.useState<string>('ALL')\n  const [isWrapped, setIsWrapped] = React.useState<boolean>(false)\n  const [copiedLogs, setCopiedLogs] = React.useState<boolean>(false)\n\n  const filteredLogs = React.useMemo(() => {\n    const query = logSearchQuery.trim().toLowerCase()\n    const level = selectedLogLevel\n\n    return node.logs.filter((log) => {\n      const matchesLevel = level === 'ALL' || log.level === level\n      const matchesQuery =\n        query === '' ||\n        log.message.toLowerCase().includes(query) ||\n        log.source.toLowerCase().includes(query) ||\n        log.timestamp.toLowerCase().includes(query)\n\n      return matchesLevel && matchesQuery\n    })\n  }, [node, logSearchQuery, selectedLogLevel])\n\n  const handleCopyNodeLogs = React.useCallback(async () => {\n    const text = node.logs.map((l) => `[${l.timestamp}] [${l.level}] [${l.source}] ${l.message}`).join('\\n')\n    try {\n      await navigator.clipboard.writeText(text)\n      setCopiedLogs(true)\n      setTimeout(() => {\n        setCopiedLogs(false)\n      }, 2000)\n    } catch {\n      // Clipboard fallback\n    }\n  }, [node])\n\n  return (\n    <Card className=\"border-border bg-card shadow-xs\">\n      <CardHeader className=\"pb-3\">\n        <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n          {/* Selected Task Metadata */}\n          <div className=\"space-y-1.5\">\n            <div className=\"flex flex-wrap items-center gap-2\">\n              <div className=\"border-primary/30 bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg border\">\n                <NodeIcon className=\"size-4\" />\n              </div>\n              <span className=\"text-foreground font-mono text-base font-semibold\">{node.name}</span>\n              <Badge className={cn('font-mono text-xs', categoryBadgeClass)}>{node.categoryLabel}</Badge>\n              <Badge variant=\"success\" className=\"gap-1 font-mono text-xs\">\n                <Check className=\"size-3\" />\n                Success\n              </Badge>\n            </div>\n\n            {/* Task Telemetry Chips */}\n            <div className=\"text-muted-foreground flex flex-wrap items-center gap-2 font-mono text-xs\">\n              <span>Operator: {node.operator}</span>\n              <span>•</span>\n              <span className=\"tabular-nums\">Duration: {node.duration}</span>\n              <span>•</span>\n              <span className=\"tabular-nums\">Retries: {node.retries}</span>\n              <span>•</span>\n              <span className=\"tabular-nums\">RAM: {node.memoryPeak}</span>\n              <span>•</span>\n              <span className=\"tabular-nums\">CPU: {node.cpuPeak}</span>\n            </div>\n          </div>\n\n          {/* Log Controls & Filters */}\n          <div className=\"flex flex-wrap items-center gap-2\">\n            {/* Search Logs Input */}\n            <div className=\"relative w-44 sm:w-56\">\n              <Search className=\"text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2\" />\n              <input\n                value={logSearchQuery}\n                onChange={(e) => setLogSearchQuery(e.target.value)}\n                type=\"text\"\n                placeholder=\"Filter logs...\"\n                className=\"border-border bg-muted/40 text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 h-8 w-full rounded-md border pr-7 pl-8 text-xs focus-visible:ring-[2px] focus-visible:outline-none\"\n              />\n              {logSearchQuery && (\n                <button\n                  type=\"button\"\n                  className=\"text-muted-foreground hover:text-foreground absolute top-1/2 right-2 -translate-y-1/2\"\n                  aria-label=\"Clear log search\"\n                  onClick={() => setLogSearchQuery('')}\n                >\n                  <X className=\"size-3.5\" />\n                </button>\n              )}\n            </div>\n\n            {/* Wrap Toggle */}\n            <Button variant=\"outline\" size=\"sm\" className=\"h-8 gap-1 text-xs\" onClick={() => setIsWrapped(!isWrapped)}>\n              <span>{isWrapped ? 'Unwrap Lines' : 'Wrap Lines'}</span>\n            </Button>\n\n            {/* Copy Logs Button */}\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              className=\"h-8 gap-1.5 text-xs font-medium\"\n              disabled={copiedLogs}\n              onClick={handleCopyNodeLogs}\n            >\n              {copiedLogs ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n              <span>{copiedLogs ? 'Logs Copied' : 'Copy Logs'}</span>\n            </Button>\n          </div>\n        </div>\n      </CardHeader>\n\n      <CardContent className=\"p-6 pt-1\">\n        <Tabs value={activeTab} onValueChange={setActiveTab} className=\"w-full\">\n          <TabsList className=\"mb-4\">\n            <TabsTrigger value=\"logs\" className=\"gap-1.5 font-mono text-xs\">\n              <Terminal className=\"size-3.5\" />\n              <span>Stdout Logs ({node.logs.length})</span>\n            </TabsTrigger>\n            <TabsTrigger value=\"params\" className=\"gap-1.5 font-mono text-xs\">\n              <Code2 className=\"size-3.5\" />\n              <span>Task Parameters</span>\n            </TabsTrigger>\n            <TabsTrigger value=\"lineage\" className=\"gap-1.5 font-mono text-xs\">\n              <GitBranch className=\"size-3.5\" />\n              <span>Lineage & Schema</span>\n            </TabsTrigger>\n            <TabsTrigger value=\"quality\" className=\"gap-1.5 font-mono text-xs\">\n              <ShieldCheck className=\"size-3.5\" />\n              <span>Quality Assertions ({node.qualityChecks.length})</span>\n            </TabsTrigger>\n          </TabsList>\n\n          {/* Tab 1: Stdout Execution Logs */}\n          <TabsContent value=\"logs\" className=\"space-y-2\">\n            {/* Filter Bar for Log Levels */}\n            <div className=\"flex flex-wrap items-center justify-between gap-2\">\n              <div className=\"flex items-center gap-1\">\n                <span className=\"text-muted-foreground mr-1 text-xs font-medium\">Level:</span>\n                {(['ALL', 'INFO', 'WARN', 'SQL', 'SUCCESS'] as const).map((lvl) => (\n                  <button\n                    key={lvl}\n                    type=\"button\"\n                    className={cn(\n                      'min-h-6 rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n                      selectedLogLevel === lvl\n                        ? 'bg-primary text-primary-foreground font-semibold'\n                        : 'border-border bg-muted/30 text-muted-foreground hover:text-foreground border',\n                    )}\n                    onClick={() => setSelectedLogLevel(lvl)}\n                  >\n                    {lvl}\n                  </button>\n                ))}\n              </div>\n\n              <div className=\"text-muted-foreground font-mono text-xs\">\n                Showing {filteredLogs.length} of {node.logs.length} lines\n              </div>\n            </div>\n\n            {/* Dark Terminal Window Container */}\n            <div className=\"overflow-hidden rounded-xl border border-zinc-800 bg-zinc-950 text-zinc-100 shadow-md\">\n              {/* Terminal Header Bar */}\n              <div className=\"flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-4 py-2 text-xs\">\n                <div className=\"flex items-center gap-2\">\n                  <div className=\"flex items-center gap-1.5\">\n                    <span className=\"bg-destructive/80 inline-block size-2.5 rounded-full\" />\n                    <span className=\"bg-warning/80 inline-block size-2.5 rounded-full\" />\n                    <span className=\"bg-success/80 inline-block size-2.5 rounded-full\" />\n                  </div>\n                  <span className=\"text-muted-foreground font-mono text-xs\">stdout &middot; {node.operator}</span>\n                </div>\n\n                <div className=\"text-muted-foreground flex items-center gap-2 font-mono text-xs\">\n                  <span>host: runner-us-east-1a</span>\n                  <span>&middot;</span>\n                  <span>exit: 0 (success)</span>\n                </div>\n              </div>\n\n              {/* Terminal Body Viewport */}\n              <div className=\"max-h-[380px] overflow-y-auto p-4 font-mono text-xs leading-relaxed\">\n                {filteredLogs.length === 0 ? (\n                  <div className=\"text-muted-foreground py-8 text-center\">\n                    No log entries matching current search or level filters.\n                  </div>\n                ) : (\n                  filteredLogs.map((log, idx) => (\n                    <div\n                      key={idx}\n                      className={cn(\n                        '-mx-1 flex items-start rounded px-1 py-0.5 hover:bg-zinc-900/60',\n                        isWrapped ? 'break-all whitespace-pre-wrap' : 'overflow-x-auto whitespace-pre',\n                      )}\n                    >\n                      {/* Line Number */}\n                      <span className=\"mr-3 w-7 shrink-0 text-right text-zinc-600 tabular-nums select-none\">\n                        {idx + 1}\n                      </span>\n\n                      {/* Timestamp */}\n                      <span className=\"text-success/90 mr-2 shrink-0 tabular-nums select-none\">[{log.timestamp}]</span>\n\n                      {/* Level Tag */}\n                      <span\n                        className={cn(\n                          'mr-2 shrink-0 font-semibold',\n                          log.level === 'INFO' && 'text-info',\n                          log.level === 'WARN' && 'text-warning',\n                          log.level === 'SQL' && 'text-chart-1',\n                          log.level === 'SUCCESS' && 'text-success font-bold',\n                          log.level === 'ERROR' && 'text-destructive font-bold',\n                        )}\n                      >\n                        [{log.level}]\n                      </span>\n\n                      {/* Source Tag */}\n                      <span className=\"text-muted-foreground mr-2 shrink-0 select-none\">[{log.source}]</span>\n\n                      {/* Message */}\n                      <span className=\"flex-1 text-zinc-200\">{log.message}</span>\n                    </div>\n                  ))\n                )}\n              </div>\n            </div>\n          </TabsContent>\n\n          {/* Tab 2: Task Parameters & Config */}\n          <TabsContent value=\"params\" className=\"space-y-4\">\n            <div className=\"border-border bg-card overflow-hidden rounded-xl border\">\n              <div className=\"border-border bg-muted/40 px-4 py-3 text-xs font-semibold\">\n                Task Configuration Manifest ({node.operator})\n              </div>\n              <div className=\"divide-border divide-y text-xs\">\n                {Object.entries(node.configParams).map(([key, val]) => (\n                  <div key={key} className=\"flex flex-col gap-1 p-3.5 sm:flex-row sm:items-center sm:justify-between\">\n                    <span className=\"text-muted-foreground font-mono font-medium sm:w-1/3\">{key}</span>\n                    <span className=\"text-foreground border-border bg-muted/30 rounded border px-2 py-1 font-mono break-all sm:w-2/3\">\n                      {val}\n                    </span>\n                  </div>\n                ))}\n              </div>\n            </div>\n          </TabsContent>\n\n          {/* Tab 3: Lineage & Schema */}\n          <TabsContent value=\"lineage\" className=\"space-y-4\">\n            <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n              {/* Upstream Dependencies */}\n              <div className=\"border-border bg-card rounded-xl border p-4\">\n                <div className=\"text-foreground flex items-center gap-2 text-xs font-semibold\">\n                  <GitCommit className=\"text-muted-foreground size-3.5\" />\n                  <span>Upstream Dependencies ({node.upstream.length})</span>\n                </div>\n                {node.upstream.length === 0 ? (\n                  <div className=\"text-muted-foreground mt-3 text-xs\">\n                    Root extraction node. No upstream parent dependencies.\n                  </div>\n                ) : (\n                  <div className=\"mt-3 space-y-2\">\n                    {node.upstream.map((up) => (\n                      <div\n                        key={up}\n                        role=\"button\"\n                        tabIndex={0}\n                        className=\"border-border bg-muted/20 hover:border-primary/40 focus-visible:ring-ring flex cursor-pointer items-center justify-between rounded-lg border p-2.5 transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                        onClick={() => onSelectNode(up)}\n                        onKeyDown={(e) => {\n                          if (e.key === 'Enter' || e.key === ' ') {\n                            e.preventDefault()\n                            onSelectNode(up)\n                          }\n                        }}\n                      >\n                        <span className=\"text-foreground font-mono text-xs font-medium\">{up}</span>\n                        <Badge variant=\"success\" className=\"text-xs\">\n                          Resolved\n                        </Badge>\n                      </div>\n                    ))}\n                  </div>\n                )}\n              </div>\n\n              {/* Downstream Dependents */}\n              <div className=\"border-border bg-card rounded-xl border p-4\">\n                <div className=\"text-foreground flex items-center gap-2 text-xs font-semibold\">\n                  <CornerDownRight className=\"text-muted-foreground size-3.5\" />\n                  <span>Downstream Consumers ({node.downstream.length})</span>\n                </div>\n                {node.downstream.length === 0 ? (\n                  <div className=\"text-muted-foreground mt-3 text-xs\">\n                    Terminal node. Emits finalized reporting dataset.\n                  </div>\n                ) : (\n                  <div className=\"mt-3 space-y-2\">\n                    {node.downstream.map((down) => (\n                      <div\n                        key={down}\n                        role=\"button\"\n                        tabIndex={0}\n                        className=\"border-border bg-muted/20 hover:border-primary/40 focus-visible:ring-ring flex cursor-pointer items-center justify-between rounded-lg border p-2.5 transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                        onClick={() => onSelectNode(down)}\n                        onKeyDown={(e) => {\n                          if (e.key === 'Enter' || e.key === ' ') {\n                            e.preventDefault()\n                            onSelectNode(down)\n                          }\n                        }}\n                      >\n                        <span className=\"text-foreground font-mono text-xs font-medium\">{down}</span>\n                        <Badge variant=\"outline\" className=\"text-xs\">\n                          Triggered\n                        </Badge>\n                      </div>\n                    ))}\n                  </div>\n                )}\n              </div>\n            </div>\n\n            {/* Target Schema Column Table */}\n            <div className=\"border-border bg-card overflow-hidden rounded-xl border\">\n              <div className=\"border-border bg-muted/40 px-4 py-3 text-xs font-semibold\">\n                Target Dataset Schema ({node.target})\n              </div>\n              <div className=\"overflow-x-auto\">\n                <table className=\"w-full text-left text-xs\">\n                  <thead className=\"border-border bg-muted/20 text-muted-foreground border-b font-mono font-medium\">\n                    <tr>\n                      <th className=\"px-4 py-2.5\">Column Name</th>\n                      <th className=\"px-4 py-2.5\">Data Type</th>\n                      <th className=\"px-4 py-2.5\">Nullable</th>\n                      <th className=\"px-4 py-2.5\">Description</th>\n                    </tr>\n                  </thead>\n                  <tbody className=\"divide-border divide-y font-mono\">\n                    {node.schemaColumns.map((col) => (\n                      <tr key={col.name} className=\"hover:bg-muted/30\">\n                        <td className=\"text-foreground px-4 py-2.5 font-semibold\">{col.name}</td>\n                        <td className=\"text-info px-4 py-2.5\">{col.type}</td>\n                        <td className=\"text-muted-foreground px-4 py-2.5\">{col.nullable ? 'YES' : 'NO'}</td>\n                        <td className=\"text-muted-foreground px-4 py-2.5 font-sans\">{col.description}</td>\n                      </tr>\n                    ))}\n                  </tbody>\n                </table>\n              </div>\n            </div>\n          </TabsContent>\n\n          {/* Tab 4: Quality Assertions */}\n          <TabsContent value=\"quality\" className=\"space-y-4\">\n            <div className=\"border-border bg-card overflow-hidden rounded-xl border\">\n              <div className=\"border-border bg-muted/40 flex items-center justify-between px-4 py-3 text-xs font-semibold\">\n                <span>Great Expectations / Assertion Test Suite</span>\n                <span className=\"text-success font-mono\">\n                  {node.qualityChecks.filter((c) => c.status === 'passed').length} / {node.qualityChecks.length} PASSED\n                </span>\n              </div>\n              <div className=\"divide-border divide-y text-xs\">\n                {node.qualityChecks.map((check) => (\n                  <div\n                    key={check.id}\n                    className=\"flex flex-col gap-2 p-4 sm:flex-row sm:items-center sm:justify-between\"\n                  >\n                    <div className=\"space-y-1\">\n                      <div className=\"flex items-center gap-2\">\n                        <Badge variant=\"success\" className=\"text-xs\">\n                          PASSED\n                        </Badge>\n                        <span className=\"text-foreground font-mono font-semibold\">{check.assertion}</span>\n                      </div>\n                      <p className=\"text-muted-foreground text-xs\">\n                        Target column: <code className=\"text-foreground font-mono\">{check.column}</code>\n                      </p>\n                    </div>\n\n                    <div className=\"text-muted-foreground flex items-center gap-4 font-mono text-xs\">\n                      <div>\n                        <span className=\"text-muted-foreground/80\">Observed:</span>\n                        <span className=\"text-foreground ml-1 font-semibold\">{check.observed}</span>\n                      </div>\n                      <div>\n                        <span className=\"text-muted-foreground/80\">Threshold:</span>\n                        <span className=\"text-muted-foreground ml-1\">{check.threshold}</span>\n                      </div>\n                    </div>\n                  </div>\n                ))}\n              </div>\n            </div>\n          </TabsContent>\n        </Tabs>\n      </CardContent>\n    </Card>\n  )\n}\n",
      "type": "registry:page",
      "target": "~/components/blocks/EtlNodeDetail.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/progress.json",
    "https://uipkge.dev/r/react/tabs.json"
  ],
  "description": "Airflow and Dagster style directed acyclic graph (DAG) pipeline visualizer with interactive node execution topologies, health metrics, and terminal stdout log inspector.",
  "categories": [
    "devops",
    "app",
    "analytics"
  ]
}