Eddy Dev Handbook
Audits

Sheets Audit

Architecture audit and analysis of the Sheet domain

Sheet Domain: Architecture Audit & Analysis

Source: This page is based on docs/SHEETS_CODE_AUDIT.md

Last Updated: January 15, 2026
Status: 🟑 Complete - Audit Finished, Optimization Opportunities Identified


Executive Summary

This document provides a comprehensive audit of the Sheet domain. Sheets are Eddy's "Trophy Room" - the destination for structured data collected during workflow runs, presented through a powerful spreadsheet-like interface built on AG-Grid.

Current State Assessment

AreaGradeStatusKey Characteristics
Data FetchingB🟑 Single endpoint, no lazy loadingβœ… Simple pattern, ⚠️ Loads all data upfront
State ManagementB+🟒 Clean with local stateβœ… React Query + useState, stable patterns
PerformanceB-🟑 Client-side filtering concernsβœ… AG-Grid efficient, ⚠️ O(NΓ—M) filter operations
Code OrganizationB+🟒 Well-structured componentsβœ… Clear separation, modular cell renderers
MaintainabilityB🟒 Good patterns, some tech debtβœ… Type-safe, ⚠️ Complex transformations, TODOs present
Separation of ConcernsB+🟒 Clear boundariesβœ… Hooks, utils, components separated

Overall Grade: B/B+ (Good - Production Ready with Optimization Opportunities)

Key Strengths:

  • βœ… Robust AG-Grid Integration - Professional data grid with rich features
  • βœ… Type-Safe Cell Renderers - 12 specialized cell renderers with proper TypeScript types
  • βœ… Comprehensive View System - Save/load custom views with filters, column order, and visibility
  • βœ… Safe Deletion Flow - Checks dependencies before deletion, warns about bound workflows/blocks
  • βœ… Export Functionality - CSV export with data sanitization

Key Opportunities:

  • ⚠️ Client-Side Filtering - All filtering happens in browser (O(NΓ—M) complexity for large datasets)
  • ⚠️ Data Transformation - Multiple transformation passes on every render
  • ⚠️ No Pagination - Loads all rows at once (could be problematic for large sheets)
  • ⚠️ Column Reordering - Complex logic with potential race conditions
  • ⚠️ No Lazy Loading - Modals and views load data eagerly

Architecture Overview

Core Purpose

The Sheet page (/workspaces/[groupId]/sheets/[sheetId]) is the primary interface for viewing and managing structured data collected from workflow runs. It serves as:

  • Data Viewer - Display rows/columns in a familiar spreadsheet interface
  • Data Manager - Edit columns, archive rows, manage views
  • Data Exporter - Export to CSV with sanitization
  • Collaboration Hub - Share sheets with team members

Technology Stack

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Sheet Page Architecture                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚
β”‚  β”‚ Next.js  β”‚  β”‚  React   β”‚  β”‚ Chakra   β”‚          β”‚
β”‚  β”‚ (Pages)  β”‚  β”‚  Query   β”‚  β”‚   UI     β”‚          β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚
β”‚                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚         AG-Grid Community                      β”‚  β”‚
β”‚  β”‚  (Data Grid, Sorting, Filtering, Columns)     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚
β”‚  β”‚  React   β”‚  β”‚ Lodash/  β”‚  β”‚   ts-    β”‚          β”‚
β”‚  β”‚  Query   β”‚  β”‚    FP    β”‚  β”‚ pattern  β”‚          β”‚
β”‚  β”‚ Builder  β”‚  β”‚(Transformβ”‚  β”‚ (Match)  β”‚          β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚
β”‚                                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

File Structure

pages/workspaces/[groupId]/sheets/[sheetId]/
  └── index.tsx                    # Entry point (947 lines)

components/sheet/
  β”œβ”€β”€ SheetTopBar.tsx              # Top control bar (381 lines)
  β”œβ”€β”€ SheetViewsDropDown.tsx       # View selector (130 lines)
  β”œβ”€β”€ ColumnModal.tsx              # Column CRUD modal (237 lines)
  β”œβ”€β”€ sheet-utils.ts               # AG-Grid utilities (113 lines)
  β”œβ”€β”€ cell-renderers/              # 12 specialized renderers
  β”‚   β”œβ”€β”€ index.tsx                # Renderer selector
  β”‚   β”œβ”€β”€ AgCellActions.tsx        # Action buttons
  β”‚   β”œβ”€β”€ AgCellDate.tsx           # Date with calendar export
  β”‚   β”œβ”€β”€ AgCellLongtext.tsx       # Modal for long text
  β”‚   β”œβ”€β”€ AgCellAttachment.tsx     # File attachments
  β”‚   β”œβ”€β”€ AgCellChecks.tsx         # Checklist items
  β”‚   └── ... (7 more)
  └── sheet-components/
      β”œβ”€β”€ AgCustomHeaderEdit.tsx   # Custom column headers
      β”œβ”€β”€ DeleteModal.tsx          # Safe deletion with deps
      β”œβ”€β”€ QBPopover.tsx            # Query builder UI
      └── ... (4 more)

hooks/
  β”œβ”€β”€ sheets.tsx                   # Sheet data hooks (204 lines)
  └── columns.tsx                  # Column CRUD hooks (131 lines)

util/
  β”œβ”€β”€ sheet.ts                     # Data transformations (532 lines)
  β”œβ”€β”€ filterRow.ts                 # Client-side filtering (556 lines)
  └── handleColumnHiding.ts        # Column visibility (52 lines)

Data Fetching Strategy

Single Endpoint Pattern

// Load all sheet data at once
const { data: sheetData } = useGetSheetDataBySheet(sheetId)
const { sheets, columns, rows, cells } = sheetData || {}

Benefits:

  • Simple, predictable pattern
  • Single network request
  • Consistent loading state

Drawbacks:

  • No lazy loading
  • Loads all data upfront
  • Performance concerns for large sheets (>1000 rows)

Cell Renderers

12 Specialized Renderers

Eddy provides type-specific cell renderers for different data types:

  1. AgCellText - Plain text
  2. AgCellLongtext - Long text with modal
  3. AgCellDate - Date picker with calendar export
  4. AgCellNumber - Numeric values
  5. AgCellSelect - Dropdown selection
  6. AgCellMultiSelect - Multiple selections
  7. AgCellChecks - Checklist items
  8. AgCellAttachment - File uploads
  9. AgCellUser - User selection
  10. AgCellRelationship - Linked records
  11. AgCellFormula - Computed values
  12. AgCellActions - Row actions (edit/delete)

Each renderer is:

  • Type-safe with proper TypeScript interfaces
  • Optimized for AG-Grid's virtual scrolling
  • Handles both read and edit modes

View System

Custom Views

Users can save custom views with:

  • Filter configurations
  • Column visibility
  • Column order
  • Sort settings

Implementation:

// Save view
const saveView = useMutation({
  mutationFn: (view) => createSheetView({
    sheetId,
    name: view.name,
    filters: view.filters,
    columnOrder: view.columnOrder,
    hiddenColumns: view.hiddenColumns
  })
})

// Load view
const applyView = (view) => {
  setColumnOrder(view.columnOrder)
  setHiddenColumns(view.hiddenColumns)
  setFilters(view.filters)
}

Performance Characteristics

Current Performance

Dataset SizePerformanceNotes
<100 rowsβœ… ExcellentNo issues
100-500 rowsβœ… GoodMinor lag on filter
500-1000 rows⚠️ AcceptableNoticeable lag on filter
>1000 rows❌ PoorSignificant lag, needs optimization

Bottlenecks

  1. Client-Side Filtering - O(NΓ—M) complexity for N rows and M filter conditions
  2. Data Transformation - Multiple passes on every render
  3. No Pagination - All rows loaded at once
  4. Column Reordering - Complex state updates

Key Features

Safe Deletion

Before deleting a sheet, the system checks for dependencies:

// Check if sheet is bound to workflows/blocks
const dependencies = await checkSheetDependencies(sheetId)

if (dependencies.workflows.length > 0) {
  showWarning(`This sheet is used by ${dependencies.workflows.length} workflows`)
}

CSV Export

Export sheet data with proper sanitization:

// Sanitize data for CSV export
const exportData = rows.map(row => ({
  ...sanitizeCellValues(row.cells),
  created_at: formatDate(row.created_at)
}))

downloadCSV(exportData, `${sheet.name}.csv`)

Optimization Opportunities

1. Server-Side Filtering

Move filtering to the backend:

// βœ… GOOD: Server-side filtering
const { data } = useGetSheetData(sheetId, {
  filters: activeFilters,
  page: currentPage,
  pageSize: 100
})

// ❌ BAD: Client-side filtering
const filteredRows = rows.filter(row => 
  applyFilters(row, activeFilters)
)

2. Pagination

Implement cursor-based pagination:

const { data, fetchNextPage } = useInfiniteQuery({
  queryKey: ['sheet', sheetId],
  queryFn: ({ pageParam }) => fetchSheetPage(sheetId, pageParam),
  getNextPageParam: (lastPage) => lastPage.nextCursor
})

3. Virtual Scrolling

AG-Grid already supports virtual scrolling, but we can optimize further by:

  • Reducing cell renderer complexity
  • Memoizing expensive computations
  • Lazy loading cell data

4. Lazy Modal Loading

Load modal data only when opened:

// βœ… GOOD: Lazy loading
const { data } = useGetRowData(rowId, {
  enabled: isModalOpen
})

// ❌ BAD: Eager loading
const { data } = useGetRowData(rowId)

On this page