Approach: Document and enforce the intentional separation between Apollo (Hasura metadata) and TanStack Query (blockchain state).
File: apps/frontend/src/graphql/queries/escrow-queries.ts
Add at top:
/**
* Hasura Escrow Metadata Queries
*
* Purpose: Query app-managed escrow transaction metadata from Hasura backend.
* This is secondary/supporting data — the authoritative escrow state lives on-chain
* and is indexed by Trustless Work Indexer.
*
* Use this for:
* - Admin visibility into all escrow_transactions table records
* - App-side funding status tracking
* - Activity feeds and audit logs
*
* DO NOT use this for:
* - Operational escrow workflows (use useEscrowsByRoleQuery or useEscrowsBySignerQuery instead)
* - On-chain state queries (use TanStack Query helpers instead)
* - Mutations (escrow mutations are on-chain; see useEscrowsMutations)
*
* Cache: Apollo InMemoryCache (subscriptions provide real-time updates)
* Related: apps/frontend/src/graphql/subscriptions/escrow-subscriptions.ts
*/File: apps/frontend/src/graphql/subscriptions/escrow-subscriptions.ts
Add at top:
/**
* Hasura Escrow Subscriptions
*
* Live updates to escrow_transactions table state.
* Use these to react to app-side changes (funding status, transaction record creation, etc).
*
* Subscriptions:
* - ESCROW_STATUS_SUBSCRIPTION: Watch a single escrow transaction's status
* - USER_ESCROW_ACTIVITY_SUBSCRIPTION: Watch all activity for a user's escrow records
*
* Cache: Apollo InMemoryCache (auto-updated by subscription)
*
* Note: On-chain escrow state is NOT guaranteed to be in sync with these records.
* If on-chain state is needed, use TanStack Query helpers (useEscrowsByRoleQuery, etc).
*/File: apps/frontend/src/components/tw-blocks/providers/ReactQueryClientProvider.tsx
Add comment before export function ReactQueryClientProvider:
/**
* TanStack Query Provider for Trustless Work Escrows
*
* Scope: All on-chain escrow queries, mutations, and caching.
* Data Source: Trustless Work Indexer API (authoritative blockchain state).
*
* This provider should wrap ONLY tw-blocks components that interact with
* on-chain escrows (queries, mutations, signer operations).
*
* Purpose:
* - Manage queries to Trustless Work Indexer (roles, signers, status, balances)
* - Handle blockchain mutations (deploy, fund, approve, dispute, release)
* - Cache and invalidate escrow state (5-min stale time, manual invalidation on success)
*
* DO NOT mix with Apollo escrow queries — they're separate data sources.
* Components should use EITHER Apollo (metadata) OR TanStack Query (on-chain), never both.
*
* Example: WalletEscrowDashboard uses only TanStack Query.
*/File: apps/frontend/src/hooks/useEscrowSubscription.ts
Add comment at top:
/**
* Hook: Subscribe to a single escrow_transaction from Hasura
*
* Data Source: Hasura escrow_transactions table (app-side metadata)
*
* Use this for:
* - Showing live updates to escrow status (for admin/monitoring)
* - Reacting to funding_status changes
* - Toast notifications on escrow updates
*
* DO NOT use this for:
* - Operational workflow checks (use useEscrowsByRoleQuery instead)
* - On-chain state validation (use Trustless Work Indexer queries)
*
* Related: apps/frontend/src/graphql/subscriptions/escrow-subscriptions.ts
*/File: apps/frontend/src/components/tw-blocks/tanstack/useEscrowsByRoleQuery.ts
Add comment at top:
/**
* Query: Fetch escrows for a specific role from Trustless Work Indexer
*
* Data Source: Trustless Work Indexer API (on-chain escrow state)
*
* Parameters:
* - role: 'approver' | 'marker' | 'releaser'
* - roleAddress: Wallet address of the user
* - isActive: Filter to active escrows only
* - Filtering: title, engagementId, status, type, amount range, dates, etc.
*
* Caching:
* - Stale time: 5 minutes
* - Invalidation: Manual via mutation.onSuccess() → queryClient.invalidateQueries(['escrows'])
*
* DO NOT use this for:
* - App metadata queries (use Apollo GET_ESCROW_TRANSACTIONS instead)
* - Hasura records (different data source entirely)
*
* Use this for:
* - Operational dashboards (WalletEscrowDashboard, role-based views)
* - Workflow decisions (show buttons only if user is approver, etc.)
* - Displaying on-chain state
*/File: apps/frontend/src/components/tw-blocks/tanstack/useEscrowsBySignerQuery.ts
Add comment at top:
/**
* Query: Fetch all escrows for a wallet signer from Trustless Work Indexer
*
* Data Source: Trustless Work Indexer API (on-chain escrow state)
*
* Similar to useEscrowsByRoleQuery but returns all roles for a given signer.
*
* Caching:
* - Stale time: 5 minutes
* - Invalidation: Manual via mutation.onSuccess()
*
* Usage: WalletEscrowDashboard calls this to fetch all signer's escrows,
* then filters by role in-memory.
*/File: apps/frontend/src/components/tw-blocks/tanstack/useEscrowsMutations.ts
Add comment at top:
/**
* Mutations: On-chain escrow operations via Trustless Work API
*
* Data Source: Trustless Work blockchain (on-chain state)
*
* Operations:
* 1. deployEscrowMutation - Create new escrow contract
* 2. updateEscrowMutation - Modify escrow parameters
* 3. fundEscrowMutation - Send funds into escrow
* 4. approveMilestoneMutation - Approver marks milestone done
* 5. changeMilestoneStatusMutation - Change milestone state
* 6. startDisputeMutation - Initiate dispute
* 7. releaseFundsMutation - Release funds from escrow
* 8. resolveDisputeMutation - Settle dispute
*
* Pattern (all mutations):
* 1. Construct unsigned transaction
* 2. Sign with user's wallet
* 3. Send signed tx to blockchain
* 4. On success: invalidateQueries(['escrows']) — refresh TanStack cache
*
* DO NOT use Apollo mutations for on-chain operations.
* Apollo escrow mutations (if any) are for app metadata only.
*/Create a reference file:
File: apps/frontend/src/components/ESCROW_COMPONENT_STACK_MATRIX.md
# Escrow Data Stack by Component
## Apollo (Hasura) Only ✅
| Component | Path | Primary Query | Purpose |
|-----------|------|---------------|---------|
| EscrowList | `components/EscrowList.tsx` | GET_ESCROW_TRANSACTIONS | List all escrow_transactions |
| ActivityFeed | `components/notifications/ActivityFeed.tsx` | USER_ESCROW_ACTIVITY_SUBSCRIPTION | Show escrow activity log |
| RealTimeEscrowStatus | `components/escrow/RealTimeEscrowStatus.tsx` | ESCROW_STATUS_SUBSCRIPTION | Live status badge |
| CacheWarmer | `components/performance/CacheWarmer.tsx` | GET_ESCROW_TRANSACTIONS | Pre-fetch on app load |
## TanStack Query (Blockchain Indexer) Only ✅
| Component | Path | Primary Query | Purpose |
|-----------|------|---------------|---------|
| WalletEscrowDashboard | `components/escrow/WalletEscrowDashboard.tsx` | useEscrowsBySignerQuery | Main user dashboard |
| EscrowOverviewCard | `components/escrow/EscrowOverviewCard.tsx` | useEscrowsBySignerQuery | Card summary view |
| InitializeEscrow | `tw-blocks/escrows/*/initialize-escrow/` | useEscrowsMutations | Deploy on-chain |
| FundEscrow | `tw-blocks/escrows/*/fund-escrow/` | useEscrowsMutations | Send funds |
| ApproveMilestone | `tw-blocks/escrows/*/approve-milestone/` | useEscrowsMutations | Approver action |
| ChangeMilestoneStatus | `tw-blocks/escrows/*/change-milestone-status/` | useEscrowsMutations | Milestone update |
| StartDispute | `tw-blocks/escrows/*/start-dispute/` | useEscrowsMutations | Escalate |
| ReleaseFunds | `tw-blocks/escrows/*/release-funds/` | useEscrowsMutations | Complete escrow |
| ResolveDispute | `tw-blocks/escrows/*/resolve-dispute/` | useEscrowsMutations | Settle dispute |
## ⚠️ Requires Review (Potential Mixed Stack)
| Component | Path | Note |
|-----------|------|------|
| EscrowNotesPanel | `components/escrow/EscrowNotesPanel.tsx` | Verify it doesn't read both Apollo + TanStack |
| stubEscrow.ts | `components/escrow/views/stubEscrow.ts` | Test stub; verify no cross-stack usage |
**Rule**: Each component uses ONE stack, never both.
If you need to display data from both sources, create a wrapper component that bridges them transparently.File: .github/pull_request_template.md (add to Escrow Data section)
## Escrow Data Stack Checklist
- [ ] If using Apollo escrow queries, NOT using TanStack escrow queries in same component
- [ ] If using TanStack escrow queries/mutations, NOT using Apollo escrow queries
- [ ] Comment in PR explains which stack is used and why
- [ ] If component touches escrow state, data source is clear from importsOption A: ESLint Rule (if tooling allows)
Create a rule file to flag components importing both:
// .eslintrc.d.ts or custom rule file
// Flag any file that imports BOTH:
// - useEscrowSubscription (Apollo)
// - useEscrowsByRoleQuery or useEscrowsBySignerQuery (TanStack)Option B: Manual CI Check (bash script)
#!/bin/bash
# Check no component imports both stacks
if grep -l "useEscrowSubscription" apps/frontend/src/components/**/*.tsx | \
xargs grep -l "useEscrowsByRoleQuery\|useEscrowsBySignerQuery"; then
echo "ERROR: Found component mixing Apollo and TanStack escrow queries"
exit 1
fiUpdate: apps/frontend/README.md or apps/frontend/docs/ARCHITECTURE.md
Add section:
## Data Fetching Architecture
### Escrow Data Stack Separation
We maintain **two independent escrow data stacks** for different purposes:
1. **Apollo Client + GraphQL** (Hasura)
- Purpose: App-side escrow metadata and activity
- Use in: Admin dashboards, activity feeds, status subscriptions
- Cache: Apollo InMemoryCache
- Files: `src/graphql/queries/escrow-queries.ts`, `src/graphql/subscriptions/escrow-subscriptions.ts`
2. **TanStack Query** (Trustless Work Indexer)
- Purpose: On-chain escrow state and operations
- Use in: User operational workflows, fund transfers, dispute resolution
- Cache: TanStack Query QueryClient
- Files: `src/components/tw-blocks/tanstack/useEscrows*.ts`
**Rule**: Components use ONE stack, never both. See [ESCROW_COMPONENT_STACK_MATRIX.md](../src/components/ESCROW_COMPONENT_STACK_MATRIX.md).
**Why separate?**
- Hasura tracks app-side metadata (who funded, when, status)
- Blockchain Indexer tracks on-chain state (roles, balances, disputes)
- They diverge: on-chain state is source-of-truth; Hasura records when app becomes aware
For details, see [ESCROW_DATA_FETCHING_RECONCILIATION.md](../../ESCROW_DATA_FETCHING_RECONCILIATION.md).- Phase 1 Complete: Comments added to all 5 files above
- Phase 2 Complete: ESCROW_COMPONENT_STACK_MATRIX.md created and reviewed
- Phase 3 Complete: PR template updated with data stack checklist
- Phase 4 Complete (optional): Lint rule or CI check in place
- Phase 5 Complete: README/Architecture docs updated with escrow stack section
- Verification: Run through component list in Phase 2; confirm no file imports both stacks
- Team Alignment: Architect/tech lead reviews and approves
- Phase 1 (Comments): 15 minutes
- Phase 2 (Matrix): 5 minutes
- Phase 3 (PR Template): 5 minutes
- Phase 4 (Lint/CI): 30 minutes (optional; skip if team prefers manual review)
- Phase 5 (Docs): 10 minutes
Total: ~65 minutes (or ~35 if skipping optional Phase 4)
✅ Clarity: Every developer knows which stack to use for which data
✅ Consistency: No accidental double-fetching or cache conflicts
✅ Maintainability: Future changes to one stack won't accidentally break another
✅ Reviews: PR checklist prevents accidental stack mixing
✅ Accountability: Clear responsibility for each component's data source
If the team later decides to converge on one stack:
- All comments added here will guide the deprecation path
- The component matrix makes it easy to identify what needs changing
- The PR checklist will help catch any mixed-stack code
See ESCROW_DATA_FETCHING_RECONCILIATION.md for details on convergence strategies.