agentleFS
bbvch-aibbvch-ai/aihub-core/.claude/skills/scaffold-composable/SKILL.md

scaffold-composable

Skill16 starsChanged 3 days agoApache-2.0View on GitHub

Generate Pinia-Colada composables for a backend API resource. Creates query and mutation composables following the established defineQuery/defineMutation patterns with SDK integration. Use when user says 'create a composable', 'scaffold composable', 'add query composable', 'generate useQuery hook', 'create mutation composable', 'Pinia-Colada setup', or 'add API composable for resource'. Do NOT use for full pages with composables (use scaffold-frontend-page) or composable code audits (use audit-frontend). Takes a resource name as argument.

We split this skill into the rules it gives an agent

15 rules. About 0 are common, 1 is distinctive, and 14 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 14

    Very specific

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

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read the frontend scope guide: packages/web/CLAUDE.md

    line 13
  2. Very specific:

    If unsure about Pinia-Colada API (defineQuery, defineMutation, useQueryCache, staleTime, etc.), use mcp__context7__query-docs with library …

    line 15
  3. Very specific:

    If SDK functions don't exist yet, warn the user to run /generate-sdk first.

    line 36
  4. Distinctive:

    Only create files for SDK operations that actually exist.

    line 49
  5. Very specific:

    Ensure composable files are in packages/web/composables/{resource}/ for Nuxt auto-import

    line 143
  6. Very specific:

    Verify every SDK call includes composable: '$fetch'

    line 144
  7. Very specific:

    Verify query keys use arrow functions: key: () => [...], not static arrays

    line 145
  8. Very specific:

    Verify mutations call queryCache.invalidateQueries() with the correct list key

    line 146
  9. Very specific:

    Verify staleTime: minutesToMilliseconds(5) is set on all queries

    line 147
  10. Very specific:

    { composable: '$fetch' }: Always pass this to SDK calls (uses Nuxt's $fetch)

    line 172
  11. Very specific:

    staleTime: Use minutesToMilliseconds(5) for standard resources

    line 174
  12. Very specific:

    enabled: Use useTenantReady() for tenant-scoped queries, useRouteReady() when a query only depends on route params (defined at …

    line 175
  13. Very specific:

    Cache invalidation: Call queryCache.invalidateQueries({ key: ['{resource}s'] }) after mutations

    line 177
  14. Very specific:

    Exports: Always wrap in defineQuery() or defineMutation() (Pinia-Colada composable factories)

    line 179
  15. Very specific:

    Types: Import DTO types from @core/sdk/client, never define manually

    line 180

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “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

Scaffold a New Composable

Generate Pinia-Colada composables for a backend API resource. The resource name should be provided via $ARGUMENTS.

Before You Start

Read the frontend scope guide: packages/web/CLAUDE.md

If unsure about Pinia-Colada API (defineQuery, defineMutation, useQueryCache, staleTime, etc.), use mcp__context7__query-docs with library ID /posva/pinia-colada to look up current documentation.

Study an existing composable directory for reference:

  • Simple query: packages/web/composables/agent/useAgentInstances.ts
  • Query with route params: packages/web/composables/agent/useAgentInstance.ts
  • Mutation with cache invalidation: packages/web/composables/agent/useCreateAgentInstance.ts
  • Delete mutation: packages/web/composables/agent/useDeleteAgentInstance.ts

Step 1: Check SDK Availability

Find the SDK functions and types for the resource in packages/web/sdk/client/. Identify:

  • GET list: e.g., getAll{Resource}s
  • GET single: e.g., get{Resource}
  • POST create: e.g., create{Resource}
  • PUT update: e.g., update{Resource}
  • DELETE: e.g., delete{Resource}
  • DTO types: e.g., Full{Resource}Dto, Create{Resource}Request

If SDK functions don't exist yet, warn the user to run /generate-sdk first.

Step 2: Create Composable Directory

packages/web/composables/{resource}/
├── use{Resource}s.ts              # List query (GET all)
├── use{Resource}.ts               # Single item query (GET by ID)
├── useCreate{Resource}.ts         # Create mutation (POST)
├── useUpdate{Resource}.ts         # Update mutation (PUT)
└── useDelete{Resource}.ts         # Delete mutation (DELETE)

Only create files for SDK operations that actually exist.

Tenant-scoped resources (everything under pages/[tenant]/service/) take tenant_id as a path parameter, put ['tenant', tenantId, ...] at the start of every query key, and gate enabled with useTenantReady(...routeParamNames) (see composables/agent/useAgentInstances.ts). Omit the tenant parts only for resources that are not tenant-scoped.

Step 3: List Query Pattern

import { type Full<Resource>Dto, getAll<Resource>s } from '@core/sdk/client'
import { useQuery } from '@pinia/colada'
import { minutesToMilliseconds } from 'date-fns'

export const use<Resource>s = defineQuery(() => {
  const { tenantId } = useTenant()

  const { data: <resource>s, isPending: <resource>sAreLoading } = useQuery<Full<Resource>Dto[]>({
    key: () => ['tenant', tenantId.value, '<resource>s'],
    staleTime: minutesToMilliseconds(5),
    enabled: useTenantReady(),
    query: async () => {
      return await getAll<Resource>s({ composable: '$fetch', path: { tenant_id: tenantId.value! } })
    },
  })
  return {
    <resource>s,
    <resource>sAreLoading,
  }
})

Step 4: Single Item Query Pattern (with route params)

import { type Full<Resource>Dto, get<Resource> } from '@core/sdk/client'
import { useQuery } from '@pinia/colada'
import { minutesToMilliseconds } from 'date-fns'

export const use<Resource> = defineQuery(() => {
  const route = useRoute()
  const { tenantId } = useTenant()

  const { data: <resource>, isPending: <resource>IsLoading } = useQuery<Full<Resource>Dto>({
    key: () => ['tenant', tenantId.value, '<resource>s', route.params.<resource>_id as string],
    staleTime: minutesToMilliseconds(5),
    enabled: useTenantReady('<resource>_id'),
    query: async () => {
      return await get<Resource>({
        composable: '$fetch',
        path: { tenant_id: tenantId.value!, <resource>_id: route.params.<resource>_id as string },
      })
    },
  })
  return {
    <resource>,
    <resource>IsLoading,
  }
})

Step 5: Mutation Pattern (create/update/delete)

import { type Create<Resource>Request, create<Resource> } from '@core/sdk/client'
import { useMutation, useQueryCache } from '@pinia/colada'

export const useCreate<Resource> = defineMutation(() => {
  const queryCache = useQueryCache()
  const {
    mutateAsync: create<Resource>Mutation,
    isPending: isCreating,
    error: createError,
  } = useMutation({
    mutation: async ({ request, tenantId }: { request: Create<Resource>Request, tenantId: string }) => {
      const result = await create<Resource>({
        composable: '$fetch',
        path: { tenant_id: tenantId },
        body: request,
      })
      queryCache.invalidateQueries({ key: ['tenant', tenantId, '<resource>s'] })
      return result
    },
  })
  return {
    create<Resource>: create<Resource>Mutation,
    isCreating,
    createError,
  }
})

Step 6: Verify

  1. Ensure composable files are in packages/web/composables/{resource}/ for Nuxt auto-import
  2. Verify every SDK call includes composable: '$fetch'
  3. Verify query keys use arrow functions: key: () => [...], not static arrays
  4. Verify mutations call queryCache.invalidateQueries() with the correct list key
  5. Verify staleTime: minutesToMilliseconds(5) is set on all queries

Examples

Typical invocation: /scaffold-composable pipeline

Result: Creates composable files in packages/web/composables/pipeline/:

  • usePipelines.ts — list query
  • usePipeline.ts — single item query with route params
  • useCreatePipeline.ts — create mutation with cache invalidation
  • useDeletePipeline.ts — delete mutation

Troubleshooting

ProblemSolution
SDK functions not foundRun /generate-sdk first to regenerate the client SDK
Query never resolvesCheck enabled flag — use useTenantReady() (tenant-scoped) or useRouteReady() for route-dependent queries
Stale data after mutationEnsure queryCache.invalidateQueries({ key: ['resources'] }) is called
Type errors on DTO importsRegenerate SDK — types may be outdated
Composable not auto-importedNuxt auto-imports from composables/ — ensure file is in the right directory

Key Conventions

  • { composable: '$fetch' }: Always pass this to SDK calls (uses Nuxt's $fetch)
  • Query keys: Hierarchical arrays ['{resource}s'], ['{resource}s', id]
  • staleTime: Use minutesToMilliseconds(5) for standard resources
  • enabled: Use useTenantReady() for tenant-scoped queries, useRouteReady() when a query only depends on route params (defined at packages/web/composables/useRouteReady.ts)
  • Cache invalidation: Call queryCache.invalidateQueries({ key: ['{resource}s'] }) after mutations
  • Naming: use{Resource}s (plural list), use{Resource} (single), useCreate{Resource} (mutation)
  • Exports: Always wrap in defineQuery() or defineMutation() (Pinia-Colada composable factories)
  • Types: Import DTO types from @core/sdk/client, never define manually

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.

Reviews can't be read right now.

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.