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
| Area | Grade | Status | Key Characteristics |
|---|---|---|---|
| Data Fetching | B | π‘ Single endpoint, no lazy loading | β Simple pattern, β οΈ Loads all data upfront |
| State Management | B+ | π’ Clean with local state | β React Query + useState, stable patterns |
| Performance | B- | π‘ Client-side filtering concerns | β AG-Grid efficient, β οΈ O(NΓM) filter operations |
| Code Organization | B+ | π’ Well-structured components | β Clear separation, modular cell renderers |
| Maintainability | B | π’ Good patterns, some tech debt | β Type-safe, β οΈ Complex transformations, TODOs present |
| Separation of Concerns | B+ | π’ 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:
- AgCellText - Plain text
- AgCellLongtext - Long text with modal
- AgCellDate - Date picker with calendar export
- AgCellNumber - Numeric values
- AgCellSelect - Dropdown selection
- AgCellMultiSelect - Multiple selections
- AgCellChecks - Checklist items
- AgCellAttachment - File uploads
- AgCellUser - User selection
- AgCellRelationship - Linked records
- AgCellFormula - Computed values
- 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 Size | Performance | Notes |
|---|---|---|
| <100 rows | β Excellent | No issues |
| 100-500 rows | β Good | Minor lag on filter |
| 500-1000 rows | β οΈ Acceptable | Noticeable lag on filter |
| >1000 rows | β Poor | Significant lag, needs optimization |
Bottlenecks
- Client-Side Filtering - O(NΓM) complexity for N rows and M filter conditions
- Data Transformation - Multiple passes on every render
- No Pagination - All rows loaded at once
- 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)