Eddy Dev Handbook
Audits

Sheet Section Audit

Architecture audit of the Sheet Section component

Sheet Section Component: Architecture Audit & Analysis

Source: This page is based on docs/SHEET_SECTION_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 Section component - an embeddable, scoped sheet viewer/editor that can be added to workflow stages. Unlike the standalone Sheet page, Sheet Sections provide filtered, contextual views of sheet data within workflow runs.

Current State Assessment

AreaGradeStatusKey Characteristics
Configuration UIB+🟢 Clean, intuitive builder UI✅ DnD reordering, visibility toggles, clear UX
Data ScopingA-🟢 Powerful filtering system✅ User + Session scopes, client-side filtering
State ManagementB🟡 Complex syncing patterns⚠️ Multiple effects, form/UI state sync
Runtime PerformanceB🟢 Good for typical use cases✅ Reuses Sheet component, ⚠️ Client-side filtering
Code OrganizationB+🟢 Well-separated concerns✅ Config/Runtime split, clear responsibilities
Modal InteractionsB-🟡 Functional but complex⚠️ Mutation-driven modal, delete during "add" flow

Overall Grade: B+ (Good - Production Ready with Minor Issues)

Key Strengths:

  • Powerful Data Scoping - Filter by user (all/current) and session (all/current) in 2x2 matrix
  • Column Visibility Control - Drag-to-reorder with visibility toggles (eye icons)
  • Integrated Sheet Component - Reuses main Sheet component for consistency
  • Modal Record Editing - Full block rendering inside record modals
  • Read-Only Mode - Flexible viewing without editing
  • Create-on-the-fly - Can create new sheets directly from config UI

Key Opportunities:

  • ⚠️ Complex State Syncing - Multiple useEffect hooks with interdependencies
  • ⚠️ Client-Side Scope Filtering - O(N) filtering on every render
  • ⚠️ Mutation-Driven Modal - Creates record on open, deletes on cancel (wasteful)
  • ⚠️ No Pagination - Inherits limitation from Sheet component
  • ⚠️ Type Safety Issues - Several any types, @ts-ignore comments
  • ⚠️ Invalidation Overkill - 5+ query invalidations on single mutation

Architecture Overview

Core Purpose

The Sheet Section is an embeddable component that displays filtered sheet data within workflow stages. It serves as:

  • Contextual Data Viewer - Show only relevant rows (current user, current session)
  • Record Manager - Add/edit/delete records through modal interface
  • Data Collection Tool - Capture structured data during workflow execution
  • Flexible Display - Configure which columns to show and in what order

Lifecycle

┌──────────────────────────────────────────────────────┐
│          Sheet Section Lifecycle                     │
└──────────────────────────────────────────────────────┘

1. CONFIGURATION (Workflow Builder)

   ├─ User adds Sheet Section to a page
   ├─ Selects/creates target sheet
   ├─ Configures column visibility & order
   ├─ Sets data scopes (user/session)
   └─ Toggles read-only mode


2. PERSISTENCE (Database)

   ├─ section.type = 'sheet'
   ├─ section.sheet_id = <selected sheet>
   └─ section.options = {
        viewableColumns: [{ id, order, value }],
        readOnly: boolean,
        userScope: 'all' | 'current',
        sessionScope: 'all' | 'current'
      }


3. RUNTIME (Session Execution)

   ├─ Fetch sheet data (all rows/columns)
   ├─ Apply scope filters (client-side)
   ├─ Render filtered rows in Sheet component
   ├─ Show "Add Record" button (if not read-only)
   └─ Handle record editing via modal

File Structure

components/
  builder/components/sectionConfig/
    └── SheetSectionConfig.tsx          # Configuration UI (423 lines)

  sections/
    └── SheetSectionBody.tsx            # Runtime renderer (322 lines)

  sheet/
    └── Sheet.tsx                       # Reusable sheet component

  renderers/
    └── BlocksRenderer.tsx              # Block rendering in modals

Data Scoping System

2x2 Scoping Matrix

Sheet Sections support powerful data scoping through two independent dimensions:

User Scope:

  • all - Show rows created by any user
  • current - Show only rows created by current user

Session Scope:

  • all - Show rows from any workflow session
  • current - Show only rows from current session

Examples:

User ScopeSession ScopeUse Case
currentcurrent"My tasks in this project"
allcurrent"All team tasks in this project"
currentall"All my tasks across all projects"
allall"All tasks ever created"

Implementation

// Apply scoping filters
const filteredRows = rows.filter(row => {
  // User scope filter
  if (userScope === 'current' && row.created_by !== currentUserId) {
    return false
  }

  // Session scope filter
  if (sessionScope === 'current' && row.workflow_run_id !== currentSessionId) {
    return false
  }

  return true
})

Configuration UI

Column Visibility & Ordering

File: SheetSectionConfig.tsx

Users can:

  1. Toggle visibility - Eye icon to show/hide columns
  2. Reorder columns - Drag-and-drop to change order
  3. See column types - Visual indicators for data types

Implementation:

// DnD Kit for drag-and-drop
const sensors = useSensors(
  useSensor(PointerSensor),
  useSensor(KeyboardSensor)
)

const handleDragEnd = (event) => {
  const { active, over } = event
  if (active.id !== over.id) {
    setColumns(arrayMove(columns, oldIndex, newIndex))
  }
}

Sheet Selection

Features:

  • Async search for existing sheets
  • Create new sheet inline
  • Clear visual feedback
// React-select with async creatable
<AsyncCreatable
  loadOptions={searchSheets}
  onCreateOption={createNewSheet}
  placeholder="Select or create sheet..."
/>

Runtime Component

File: SheetSectionBody.tsx

Responsibilities:

  1. Fetch sheet data
  2. Apply scope filters
  3. Render Sheet component
  4. Handle "Add Record" button
  5. Manage record editing modal

Key Pattern:

// Reuse Sheet component with scoped data
<Sheet
  sheetId={section.sheet_id}
  rows={filteredRows}
  columns={visibleColumns}
  readOnly={section.options.readOnly}
  onRowClick={openRecordModal}
/>

Current Pattern (Mutation-Driven)

Flow:

  1. User clicks "Add Record"
  2. System creates empty row in database
  3. Modal opens with new row ID
  4. User fills in data via blocks
  5. If user cancels, row is deleted

Issues:

  • Wasteful database writes
  • Orphaned rows if modal crashes
  • Complex cleanup logic

Proposed Pattern (Optimistic)

Flow:

  1. User clicks "Add Record"
  2. Modal opens with local state
  3. User fills in data
  4. On save, create row in database
  5. On cancel, discard local state

Benefits:

  • No wasteful writes
  • Simpler cleanup
  • Better UX (instant modal open)

Performance Characteristics

Current Performance

Dataset SizePerformanceNotes
<100 rows✅ ExcellentNo issues
100-500 rows✅ GoodMinor lag on filter
500-1000 rows⚠️ AcceptableNoticeable lag
>1000 rows❌ PoorSignificant lag

Bottlenecks

  1. Client-Side Filtering - O(N) filtering on every render
  2. No Pagination - Inherits from Sheet component
  3. Query Invalidation - 5+ invalidations on single mutation
  4. State Syncing - Multiple useEffect hooks

State Management

Current Pattern

Multiple State Sources:

  • React Hook Form (configuration)
  • Local useState (UI state)
  • React Query (server state)
  • Zustand (global state)

Syncing Issues:

// ⚠️ Complex syncing with multiple effects
useEffect(() => {
  if (formData.columns !== uiColumns) {
    setUIColumns(formData.columns)
  }
}, [formData.columns])

useEffect(() => {
  if (uiColumns !== formData.columns) {
    setValue('columns', uiColumns)
  }
}, [uiColumns])

Proposed Pattern

Single Source of Truth:

// ✅ GOOD: Single state source
const { columns, setColumns } = useFieldArray({
  control,
  name: 'columns'
})

// No separate UI state needed

Optimization Opportunities

1. Server-Side Scoping

Move scope filtering to backend:

// ✅ GOOD: Server-side scoping
const { data } = useGetSheetData(sheetId, {
  userScope: 'current',
  sessionScope: 'current',
  userId: currentUserId,
  sessionId: currentSessionId
})

// ❌ BAD: Client-side scoping
const filtered = rows.filter(applyScopes)

2. Optimistic Modal Pattern

Implement optimistic UI for record creation:

// ✅ GOOD: Optimistic creation
const [draftRow, setDraftRow] = useState(null)

const openModal = () => {
  setDraftRow({ /* empty row */ })
}

const saveRow = () => {
  createRow(draftRow)
}

3. Reduce Invalidations

Invalidate only necessary queries:

// ✅ GOOD: Targeted invalidation
queryClient.invalidateQueries(['sheet', sheetId])

// ❌ BAD: Broad invalidation
queryClient.invalidateQueries(['sheet'])
queryClient.invalidateQueries(['cells'])
queryClient.invalidateQueries(['rows'])
// ... (5+ invalidations)

On this page