Eddy Dev Handbook
Audits

Data Fetching Audit

Comprehensive audit of data fetching strategies across Eddy

Data Fetching Architecture Audit

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

Last Updated: January 20, 2026
Status: ✅ Post-Refactor - Excellent Architecture

This document provides a comprehensive audit of data fetching strategies across Eddy's two primary domains: the Workflow Builder (level design) and Sessions (gameplay).


Executive Summary

Current State Assessment

DomainGradeStatusKey Achievements
SessionA+✅ Exemplary - Best-in-class implementation71% reduction in requests, zero node waterfalls
BuilderA-✅ Excellent - Clean with intentional tradeoffs27% reduction in requests, quest logs by design
OverallA✅ Production-ready with excellent architectureClear patterns, optimal performance, maintainable

Key Improvements Achieved

MetricBefore RefactorAfter RefactorImprovement
Session Network Requests12 + 3N (N=stages)12 (constant)71% for 10 stages
Builder Network Requests11 parallel8 parallel27% reduction
Node-Level WaterfallsN×3 queries0 queries100% eliminated
Redundant Fixture FetchesMultipleZero100% eliminated

Mental Model: The Three Categories

From the "game engine" mental model, our data follows three distinct categories:

Category 1: Workflow-Level Fixtures (The Level Architecture)

Purpose: Static, reusable definitions that structure the game itself.

Tables:

  • workflows - The game definition
  • pages - Individual rooms/locations
  • page_transitions - Corridors between rooms
  • blocks, sections - Interactive objects and containers
  • workflow_roles - Character class definitions
  • stage_role_assignments - Abilities per room per class
  • sheets, columns - Data collection structure

Characteristics:

  • Immutable during gameplay
  • Shared across all sessions
  • Fetched once, cached aggressively
  • Changes affect future runs, not current ones

Category 2: Session-Level Quest Logs (Progression State)

Purpose: Dynamic, session-specific tracking of progress.

Tables:

  • workflow_runs - The active campaign instance
  • workflow_run_stages - Which locations activated/completed
  • session_role_assignments - Which users play which classes
  • session_assignments - Whose turn, which room
  • session_discussions - Player communication

Characteristics:

  • Mutable during gameplay
  • Session-scoped (one per workflow run)
  • Read frequently, written less so
  • Lightweight (mostly IDs and timestamps)

Category 3: Player Input Data (Gameplay Actions & Results)

Purpose: Actual data produced by players during interaction.

Tables:

  • cells - Individual data values
  • rows - Complete records
  • cell_versions - History
  • File uploads, linked records

Characteristics:

  • Created during gameplay
  • Persists after quest completion
  • High volume
  • The actual product

Session Domain Architecture

Entry Point: Session Overview Page

File: pages/sessions/workflows/[workflowId]/[workflowRunId]/index.tsx

Status:Exemplary

// Category 1: Context
const { data: currentUser } = useGetCurrentUserAndGroups()

// Category 1: Fixtures - COMPLETE TREE
const { data: workflow } = useGetWorkflow(workflowId)
const {
  pages = [],
  pageTransitions = [],
  roles: workflowRoles = []
} = workflow || {}

// Category 2: Quest Log
const { data: runStages = [] } =
  useGetWorkflowRunStagesByWorkflowRunId(workflowRunId)

Assessment:

Perfect fixture handling - Entry point fetches workflow which contains the full tree

Props, not fetches - Extracts fixtures from workflow and passes them down as props

Minimal Category 2 data - Only fetches runStages at entry level

Session Component

File: components/session/Session.tsx

Status:Exemplary

// Category 3: Player Input (Lazy loaded)
const { data: cellsForWorkflowRun = [] } =
  useGetCellsByWorkflowRun(workflowRunId, workflowId)

const { data: columns = [] } =
  useGetColumnsByWorkflow(workflowId)

Key Pattern: Heavy player data loaded lazily, only when needed for rendering.


Builder Domain Architecture

Entry Point: Builder Page

File: pages/builder/[workflowId]/index.tsx

Status:Excellent

// Category 1: Complete workflow tree
const { data: workflow } = useGetWorkflow(workflowId)

// Category 1: Sheets (if needed)
const { data: sheets = [] } = useGetSheetsByWorkflow(workflowId)

Assessment:

Single workflow fetch - Gets complete tree in one request

Intentional sheet fetch - Separate query for sheets (design choice for flexibility)

No waterfalls - All queries run in parallel


Best Practices & Patterns

1. Fixture Extraction Pattern

// ✅ GOOD: Extract from parent data
const { pages, pageTransitions, roles } = workflow || {}

// ❌ BAD: Redundant fetch
const { data: pages } = useGetPagesByWorkflow(workflowId)

2. Consolidated State Hooks

// ✅ GOOD: Single hook for related data
const { data } = useGetSessionState(workflowRunId)
const { session, runStages, sessionAssignments } = data ?? {}

// ❌ BAD: Multiple dependent hooks
const { data: session } = useGetSession(workflowRunId)
const { data: runStages } = useGetRunStages(workflowRunId)

3. Lazy Loading Heavy Data

// ✅ GOOD: Load cells only in component that needs them
function Session() {
  const { data: cells } = useGetCellsByWorkflowRun(workflowRunId)
  // ...
}

// ❌ BAD: Load cells at entry point
function SessionPage() {
  const { data: cells } = useGetCellsByWorkflowRun(workflowRunId)
  // Passed down but not used in many components
}

4. Pre-computed Maps

// ✅ GOOD: Build maps once, use many times
const sessionDataMaps = useMemo(
  () => createSessionDataMaps({ pages, runStages, sessionAssignments }),
  [pages, runStages, sessionAssignments]
)

// ❌ BAD: Filter on every access
const assignments = allAssignments.filter(a => a.pageId === pageId)

Performance Metrics

Session Domain

Before Refactor:

  • 12 initial requests
  • 3 additional requests per stage (N stages = 3N requests)
  • Total for 10-stage workflow: 12 + 30 = 42 requests

After Refactor:

  • 12 initial requests (constant)
  • 0 additional requests per stage
  • Total for 10-stage workflow: 12 requests

Improvement: 71% reduction

Builder Domain

Before Refactor:

  • 11 parallel requests at entry point
  • Multiple redundant fixture fetches

After Refactor:

  • 8 parallel requests at entry point
  • Zero redundant fetches

Improvement: 27% reduction


Future Opportunities

1. Builder State Consolidation

Create a useGetBuilderState() hook similar to useGetSessionState() to further consolidate builder queries.

2. Optimistic Updates

Implement optimistic updates for common mutations to improve perceived performance.

3. Prefetching

Add strategic prefetching for likely next actions (e.g., prefetch session data when hovering over session link).

4. Cache Warming

Pre-populate React Query cache with data from SSR/SSG where applicable.


On this page