agentleFS
GitHubKnown publishergithub/replica/starters/coreai/.agents/skills/state-management/SKILL.md

state-management

Skill117 starsChanged 10 days agoMITView on GitHub

CoreAI Sandbox's TanStack Query + mock-JSON data layer. USE WHEN: editing data-fetching hooks (`app/hooks/use*.ts`), adding mock data in `app/data/`, or wiring a component to query state.

We split this skill into the rules it gives an agent

5 rules. 0 are common, 5 are distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 21 edited versions we read keep these.

  • 5

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Low Complexity: - No backend needed

    line 306
  2. Distinctive:

    When moving components to production:

    line 311
  3. Distinctive:

    Add resolver pattern and authentication

    line 314
  4. Distinctive:

    Add SSR/loader prefetching

    line 315
  5. Distinctive:

    Add proper error handling and retry logic

    line 317

“Common” is measured: kept by at least 6 in 10 of the 21 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

State Management Instructions — CoreAI Sandbox Demo

Paths below are relative to this app's directory.

Overview

The app uses TanStack Query v5 hooks over local JSON data to provide client-side loading, filtering, and caching for prototypes.

Architecture

1. QueryProvider Setup

The app is wrapped with QueryProvider in app/root.tsx:

import { QueryProvider } from './core/datafetching/QueryProvider';

export default function App() {
  return (
    <QueryProvider>
      <AppContent>
        <Outlet />
      </AppContent>
    </QueryProvider>
  );
}

Configuration (app/core/datafetching/QueryProvider.tsx):

  • staleTime: 5 minutes - Data is considered fresh
  • gcTime: 10 minutes - Garbage collection time
  • retry: false - Don't retry failed requests
  • refetchOnWindowFocus: false - Don't refetch on focus

2. Mock Data Layer

Location: app/data/

Files:

  • agents.json - agents with varied lifecycle statuses
  • tools.json - tools with categories and publish status
  • models.json - models with provider and category metadata
  • home.json - Home page endpoints, model actions, carousel models, and recent work
  • index.ts - TypeScript interfaces and utility functions

Data Types:

export interface Agent {
  id: string;
  name: string;
  description: string;
  status: 'draft' | 'published' | 'deployed';
  tags: string[];
  owner: string;
  createdDate: string; // ISO 8601
  updatedDate: string; // ISO 8601
  version: string; // Semantic version
}

export interface Tool {
  mark: string;
  initialsColor: string;
  id: string;
  name: string;
  description: string;
  category: ToolCategory;
  isPublished: boolean;
}

Utility Functions:

// Filter agents by status
filterAgentsByStatus(agents: Agent[], status: AgentStatus): Agent[]

// Filter tools by category
filterToolsByCategory(tools: Tool[], category: ToolCategory): Tool[]

// Filter tools by published status
filterToolsByPublished(tools: Tool[], isPublished: boolean): Tool[]

3. Custom Hooks Pattern

Location: app/hooks/

Custom hooks wrap TanStack Query's useQuery and import JSON data directly.

Example — useAgents.ts:

import type { Agent, AgentStatus } from '~/data';
import { useQuery } from '@tanstack/react-query';
import { agents, filterAgentsByStatus } from '~/data';

async function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export interface UseAgentsOptions {
  status?: AgentStatus;
}

export function useAgents(options?: UseAgentsOptions) {
  return useQuery({
    queryKey: ['agents', options?.status],
    queryFn: async (): Promise<Agent[]> => {
      // Simulate network delay (400ms)
      await delay(400);

      // Return filtered or all agents
      if (options?.status) {
        return filterAgentsByStatus(agents, options.status);
      }
      return agents;
    },
  });
}

Available Hooks:

  • useAgents(options?) - Fetch agents, optionally filtered by status
  • useTools(options?) - Fetch tools, optionally filtered by category/publish status
  • useHomeData() - Fetch mock home page content

Query Keys:

['agents', status?]           // e.g., ['agents', 'draft']
['tools', category?, isPublished?]  // e.g., ['tools', 'Data', true]
['home']                     // Home page mock content

4. Component Usage Pattern

Components use custom hooks directly in route components:

import { useAgents } from '~/hooks/useAgents';
import { CollectionPage } from '~/components/common';
import type { TableColumnDefinition } from '~/components/common';
import type { Agent } from '~/data';

export default function BuildAgents() {
  const { data: agents, isLoading } = useAgents({ status: 'draft' });
  const columns: TableColumnDefinition<Agent>[] = [
    {
      columnId: 'name',
      renderHeaderCell: () => 'Name',
      renderCell: (agent) => agent.name,
      compare: (first, second) => first.name.localeCompare(second.name),
    },
  ];

  return (
    <CollectionPage
      headerProps={{ title: 'Agents' }}
      dataGridProps={{
        items: agents ?? [],
        isLoading,
        columns,
        getRowId: (agent) => agent.id,
      }}
    />
  );
}

5. Route-Specific Filtering

Different routes show different subsets of the same data:

RouteHook UsageFilter
/build/agentsuseAgents() via AgentListAll agents (50)
/build/agents/draftsuseAgents() via AgentList, then local status filteringDraft agents (14)
/build/agents/publisheduseAgents() via AgentList, then local status filteringPublished agents (19)
/operate/agentsuseAgents({ status: 'deployed' })Deployed agents (17)
/build/toolsuseTools()All tools (35)
/discover/toolsDirect tools import mapped into catalog itemsAll tools (35; 29 published)

6. Loading States

TanStack Query provides automatic loading state management:

import { DataGridWithFilters } from '~/components/common';

const { data, isLoading, error } = useAgents();

// The app-owned grid handles loading state; columns are defined by the consuming component.
<DataGridWithFilters columns={columns} isLoading={isLoading} items={data ?? []} />;

States Available:

  • isLoading - Initial fetch in progress
  • isFetching - Any fetch in progress (including background refetch)
  • isError - Query failed
  • error - Error object if query failed
  • data - Query result data

Best Practices

DO:

  • ✅ Use TanStack Query for ALL data fetching
  • ✅ Use custom hooks (useAgents, useTools) in components
  • ✅ Pass query results to the app-owned DataGridWithFilters (or a page composition's dataGridProps) with isLoading; the underlying CoreAI DataGrid does not supply this app-owned loading API
  • ✅ Provide sensible empty states
  • ✅ Use consistent query keys for caching
  • ✅ Filter data via hook options when possible

DON'T:

  • ❌ Don't fetch data in useEffect
  • ❌ Don't use useState for server data
  • ❌ Don't call TanStack Query's useQuery directly - use custom hooks
  • ❌ Don't mutate imported JSON data
  • ❌ Don't add SSR/loader patterns (this is client-only)
  • ❌ Don't add real API calls or authentication

Simulated Delays

The mock query hooks useAgents, useTools, and useHomeData include a 400ms simulated delay:

async function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

This creates realistic loading states without actual network requests.

Adding New Data

1. Create JSON File

// app/data/newentity.json
[
  { "id": "entity-001", "name": "Example", ... }
]

2. Add Type to app/data/index.ts

export interface NewEntity {
  id: string;
  name: string;
  // ...
}

import newEntityData from './newentity.json';
export const newEntities: NewEntity[] = newEntityData as NewEntity[];

3. Create Custom Hook

// app/hooks/useNewEntity.ts
import { useQuery } from '@tanstack/react-query';
import { newEntities } from '~/data';

async function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export function useNewEntity() {
  return useQuery({
    queryKey: ['newEntity'],
    queryFn: async () => {
      await delay(400);
      return newEntities;
    },
  });
}

4. Use in Component

import { useNewEntity } from '~/hooks/useNewEntity';

const { data, isLoading } = useNewEntity();

Architecture Benefits

This simplified architecture provides:

  1. Consistent Patterns - Easy to port components to production
  2. Realistic UX - Loading states, caching behavior
  3. Type Safety - TypeScript throughout
  4. Low Complexity - No backend needed
  5. Fast Iteration - Change JSON, see results immediately

Migration to Production

When moving components to production:

  1. Custom hooks stay similar but fetch from real APIs
  2. Add resolver pattern and authentication
  3. Add SSR/loader prefetching
  4. Replace JSON imports with HTTP calls
  5. Add proper error handling and retry logic
  6. Configure appropriate staleTime per query

Related Files

  • app/core/datafetching/QueryProvider.tsx - TanStack Query provider
  • app/data/index.ts - Type definitions and exports
  • app/data/agents.json - Mock agent data
  • app/data/tools.json - Mock tool data
  • app/data/models.json - Mock model data
  • app/data/home.json - Mock home content
  • app/hooks/useAgents.ts - Agents data hook
  • app/hooks/useTools.ts - Tools data hook
  • app/hooks/useHomeData.ts - Home content data hook

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.