@baldurpan/create-ai-workflow 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/dist/agents-block.js +70 -0
  4. package/dist/bin.js +14 -0
  5. package/dist/check/markdown.js +47 -0
  6. package/dist/check/parse.js +113 -0
  7. package/dist/check/rules.js +245 -0
  8. package/dist/cli.js +109 -0
  9. package/dist/commands/check.js +41 -0
  10. package/dist/commands/install.js +76 -0
  11. package/dist/commands/standards-add.js +145 -0
  12. package/dist/commands/update.js +214 -0
  13. package/dist/layout.js +115 -0
  14. package/dist/log.js +20 -0
  15. package/dist/manifest.js +53 -0
  16. package/dist/paths.js +41 -0
  17. package/package.json +37 -0
  18. package/templates/blocks/agents-block.md +30 -0
  19. package/templates/claude/agents/planner.agent.md +60 -0
  20. package/templates/claude/agents/reviewer.agent.md +77 -0
  21. package/templates/context/README.md +81 -0
  22. package/templates/context/plan-template.md +69 -0
  23. package/templates/context/plan-template.notes.md +55 -0
  24. package/templates/context/roles/coder.md +76 -0
  25. package/templates/context/workflow.md +143 -0
  26. package/templates/skills/feature-close/SKILL.md +102 -0
  27. package/templates/skills/feature-implement/SKILL.md +142 -0
  28. package/templates/skills/feature-plan/SKILL.md +138 -0
  29. package/templates/skills/feature-status/SKILL.md +86 -0
  30. package/templates/skills/onboard/SKILL.md +122 -0
  31. package/templates/skills/orchestrate/SKILL.md +92 -0
  32. package/templates/skills/roadmap/SKILL.md +97 -0
  33. package/templates/standards/.source +8 -0
  34. package/templates/standards/README.md +104 -0
  35. package/templates/standards/architecture/api-design.md +212 -0
  36. package/templates/standards/architecture/dependency-boundaries.md +104 -0
  37. package/templates/standards/architecture/feature-driven.md +102 -0
  38. package/templates/standards/architecture/folder-structure.md +108 -0
  39. package/templates/standards/architecture/monorepos.md +107 -0
  40. package/templates/standards/architecture/refactoring.md +97 -0
  41. package/templates/standards/architecture/shared-code.md +81 -0
  42. package/templates/standards/docs/PHP-SPEC.md +1195 -0
  43. package/templates/standards/docs/SPEC.md +540 -0
  44. package/templates/standards/examples/bad/data-fetching.tsx +52 -0
  45. package/templates/standards/examples/bad/derived-state.tsx +60 -0
  46. package/templates/standards/examples/bad/feature-component.tsx +80 -0
  47. package/templates/standards/examples/bad/prop-drilling.tsx +70 -0
  48. package/templates/standards/examples/good/data-fetching.tsx +53 -0
  49. package/templates/standards/examples/good/derived-state.tsx +52 -0
  50. package/templates/standards/examples/good/feature-component.tsx +141 -0
  51. package/templates/standards/examples/good/invoice.schema.ts +20 -0
  52. package/templates/standards/examples/good/prop-drilling.tsx +83 -0
  53. package/templates/standards/philosophy/ai-agent-behavior.md +93 -0
  54. package/templates/standards/philosophy/core-principles.md +49 -0
  55. package/templates/standards/philosophy/incremental-abstraction.md +56 -0
  56. package/templates/standards/philosophy/maintainability.md +66 -0
  57. package/templates/standards/philosophy/readability.md +87 -0
  58. package/templates/standards/php/README.md +62 -0
  59. package/templates/standards/php/anti-patterns.md +259 -0
  60. package/templates/standards/php/architecture.md +241 -0
  61. package/templates/standards/php/frameworks.md +197 -0
  62. package/templates/standards/php/rules.md +205 -0
  63. package/templates/standards/php/security.md +190 -0
  64. package/templates/standards/php/testing.md +139 -0
  65. package/templates/standards/php/tooling.md +245 -0
  66. package/templates/standards/react/accessibility.md +130 -0
  67. package/templates/standards/react/anti-patterns.md +187 -0
  68. package/templates/standards/react/component-design.md +139 -0
  69. package/templates/standards/react/error-boundaries.md +195 -0
  70. package/templates/standards/react/forms.md +147 -0
  71. package/templates/standards/react/hooks.md +88 -0
  72. package/templates/standards/react/memoization.md +87 -0
  73. package/templates/standards/react/state-management.md +123 -0
  74. package/templates/standards/react/testing.md +118 -0
  75. package/templates/standards/react/use-effect.md +110 -0
  76. package/templates/standards/security/api-security.md +136 -0
  77. package/templates/standards/security/auth.md +112 -0
  78. package/templates/standards/security/secrets.md +105 -0
  79. package/templates/standards/security/validation.md +89 -0
  80. package/templates/standards/templates/.editorconfig +16 -0
  81. package/templates/standards/templates/.nvmrc +1 -0
  82. package/templates/standards/templates/AGENTS.md +46 -0
  83. package/templates/standards/templates/CLAUDE.md +3 -0
  84. package/templates/standards/templates/_dot_gitignore +50 -0
  85. package/templates/standards/templates/biome.json +53 -0
  86. package/templates/standards/templates/eslint.config.js +61 -0
  87. package/templates/standards/templates/project-readme.md +76 -0
  88. package/templates/standards/templates/pull-request-template.md +46 -0
  89. package/templates/standards/templates/tsconfig.json +35 -0
  90. package/templates/standards/tooling/biome.md +92 -0
  91. package/templates/standards/tooling/ci.md +128 -0
  92. package/templates/standards/tooling/dates.md +194 -0
  93. package/templates/standards/tooling/dependencies.md +90 -0
  94. package/templates/standards/tooling/nx.md +113 -0
  95. package/templates/standards/tooling/observability.md +183 -0
  96. package/templates/standards/tooling/prisma.md +152 -0
  97. package/templates/standards/tooling/shadcn.md +115 -0
  98. package/templates/standards/tooling/tailwind.md +135 -0
  99. package/templates/standards/tooling/tanstack-query.md +214 -0
  100. package/templates/standards/tooling/tanstack-router.md +365 -0
  101. package/templates/standards/tooling/tanstack.md +31 -0
  102. package/templates/standards/tooling/vite.md +135 -0
  103. package/templates/standards/typescript/anti-patterns.md +107 -0
  104. package/templates/standards/typescript/error-handling.md +216 -0
  105. package/templates/standards/typescript/naming.md +78 -0
  106. package/templates/standards/typescript/rules.md +159 -0
  107. package/templates/standards/typescript/tsconfig/base.json +32 -0
  108. package/templates/standards/typescript/tsconfig/nextjs.json +12 -0
  109. package/templates/standards/typescript/tsconfig/react.json +9 -0
  110. package/templates/standards/typescript/validation.md +104 -0
  111. package/templates/stubs/executors.md +49 -0
  112. package/templates/stubs/findings.md +40 -0
  113. package/templates/stubs/history.md +13 -0
  114. package/templates/stubs/roadmap.md +29 -0
  115. package/templates/stubs/stack.md +35 -0
  116. package/templates/stubs/verify.md +48 -0
@@ -0,0 +1,152 @@
1
+ # Prisma
2
+
3
+ ## Purpose
4
+
5
+ Prisma is the ORM of choice for TypeScript backend work. It provides type-safe database access with generated types derived from the schema.
6
+
7
+ ## Key Principles
8
+
9
+ - Schema is the source of truth — all types are generated from it
10
+ - Use generated Prisma types directly — do not duplicate them as DTOs
11
+ - Use `prisma.$transaction` for multi-step operations that must succeed or fail together
12
+ - Validate external input before passing to Prisma
13
+
14
+ ## Schema Conventions
15
+
16
+ ```prisma
17
+ // prisma/schema.prisma
18
+
19
+ generator client {
20
+ provider = "prisma-client-js"
21
+ }
22
+
23
+ datasource db {
24
+ provider = "postgresql"
25
+ url = env("DATABASE_URL")
26
+ }
27
+
28
+ model User {
29
+ id String @id @default(cuid())
30
+ email String @unique
31
+ name String
32
+ role Role @default(MEMBER)
33
+ createdAt DateTime @default(now())
34
+ updatedAt DateTime @updatedAt
35
+
36
+ invoices Invoice[]
37
+
38
+ @@map("users") // snake_case table name
39
+ }
40
+
41
+ model Invoice {
42
+ id String @id @default(cuid())
43
+ title String
44
+ amount Decimal @db.Decimal(10, 2)
45
+ status InvoiceStatus @default(DRAFT)
46
+ dueDate DateTime
47
+ userId String
48
+ user User @relation(fields: [userId], references: [id])
49
+ createdAt DateTime @default(now())
50
+ updatedAt DateTime @updatedAt
51
+
52
+ @@map("invoices")
53
+ }
54
+
55
+ enum Role {
56
+ ADMIN
57
+ MEMBER
58
+ VIEWER
59
+ }
60
+
61
+ enum InvoiceStatus {
62
+ DRAFT
63
+ OPEN
64
+ PAID
65
+ VOID
66
+ }
67
+ ```
68
+
69
+ ## Conventions
70
+
71
+ - Use `cuid()` or `uuid()` for IDs — not auto-increment integers (avoids enumeration)
72
+ - Always include `createdAt` and `updatedAt` with `@default(now())` and `@updatedAt`
73
+ - Use `@@map` to keep table names snake_case while model names are PascalCase
74
+ - Use `Decimal` for monetary values — never `Float`
75
+
76
+ ## Client Singleton
77
+
78
+ ```ts
79
+ // lib/db.ts
80
+ import { PrismaClient } from "@prisma/client";
81
+
82
+ const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
83
+
84
+ export const db =
85
+ globalForPrisma.prisma ??
86
+ new PrismaClient({
87
+ log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
88
+ });
89
+
90
+ if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;
91
+ ```
92
+
93
+ ## Using Generated Types
94
+
95
+ ```ts
96
+ import type { Invoice, User, InvoiceStatus } from "@prisma/client";
97
+
98
+ // Use Prisma's utility types for partial shapes
99
+ import type { Prisma } from "@prisma/client";
100
+
101
+ type InvoiceWithUser = Prisma.InvoiceGetPayload<{
102
+ include: { user: true };
103
+ }>;
104
+ ```
105
+
106
+ ## Migrations
107
+
108
+ ```bash
109
+ # Create a migration from schema changes
110
+ pnpm prisma migrate dev --name add_invoice_notes
111
+
112
+ # Apply migrations in production
113
+ pnpm prisma migrate deploy
114
+
115
+ # Reset development database
116
+ pnpm prisma migrate reset
117
+
118
+ # Regenerate the Prisma client after schema change
119
+ pnpm prisma generate
120
+ ```
121
+
122
+ ## Transactions
123
+
124
+ ```ts
125
+ const [user, invoice] = await db.$transaction([
126
+ db.user.create({ data: userData }),
127
+ db.invoice.create({ data: { ...invoiceData, userId: user.id } }),
128
+ ]);
129
+
130
+ // For dependent operations
131
+ await db.$transaction(async (tx) => {
132
+ const user = await tx.user.create({ data: userData });
133
+ const invoice = await tx.invoice.create({
134
+ data: { ...invoiceData, userId: user.id },
135
+ });
136
+ return { user, invoice };
137
+ });
138
+ ```
139
+
140
+ ## DO NOT
141
+
142
+ - Expose the Prisma client directly to API route handlers — wrap in a service layer
143
+ - Use raw `$queryRaw` without parameterized inputs (SQL injection risk)
144
+ - Store monetary values as `Float`
145
+ - Duplicate Prisma types as hand-written DTOs
146
+
147
+ ## See Also
148
+
149
+ - [`../typescript/validation.md`](../typescript/validation.md) — Zod alongside generated Prisma types
150
+ - [`dates.md`](dates.md) — DateTime columns and timezone handling
151
+ - [`../typescript/error-handling.md`](../typescript/error-handling.md) — wrapping database errors
152
+ - [`../security/api-security.md`](../security/api-security.md) — SQL injection prevention
@@ -0,0 +1,115 @@
1
+ # shadcn/ui
2
+
3
+ ## Purpose
4
+
5
+ shadcn/ui provides accessible, unstyled UI primitives built on Radix UI. Components are copied into your codebase — you own and modify them directly.
6
+
7
+ ## Key Properties
8
+
9
+ - Built on Radix UI — keyboard navigation and ARIA patterns are correct by default
10
+ - Styled with Tailwind CSS + CSS variables
11
+ - Not a dependency — components live in your `src/components/ui/` directory
12
+ - Fully customizable — modify any component without fighting against the library
13
+
14
+ ## Setup
15
+
16
+ ```bash
17
+ pnpm dlx shadcn@latest init
18
+ ```
19
+
20
+ This sets up:
21
+ - `components.json` — config
22
+ - `src/components/ui/` — where components are added
23
+ - CSS variable theme in `globals.css`
24
+
25
+ ## Adding Components
26
+
27
+ ```bash
28
+ pnpm dlx shadcn@latest add button
29
+ pnpm dlx shadcn@latest add dialog
30
+ pnpm dlx shadcn@latest add form
31
+ pnpm dlx shadcn@latest add table
32
+ ```
33
+
34
+ Components are added to `src/components/ui/`. They are yours to modify.
35
+
36
+ ## Customization
37
+
38
+ ### Via CSS Variables (Preferred)
39
+
40
+ Edit the CSS variables in `globals.css`:
41
+
42
+ ```css
43
+ @layer base {
44
+ :root {
45
+ --primary: 221.2 83.2% 53.3%;
46
+ --primary-foreground: 210 40% 98%;
47
+ --radius: 0.5rem;
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### Via Component Modification
53
+
54
+ Fork the component directly in `src/components/ui/`. Changes survive upgrades because you own the file.
55
+
56
+ ## Usage with Forms
57
+
58
+ shadcn/ui's `Form` component wraps React Hook Form:
59
+
60
+ ```tsx
61
+ import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "~/components/ui/form";
62
+ import { useForm } from "react-hook-form";
63
+ import { zodResolver } from "@hookform/resolvers/zod";
64
+
65
+ const form = useForm({
66
+ resolver: zodResolver(schema),
67
+ });
68
+
69
+ <Form {...form}>
70
+ <form onSubmit={form.handleSubmit(onSubmit)}>
71
+ <FormField
72
+ control={form.control}
73
+ name="title"
74
+ render={({ field }) => (
75
+ <FormItem>
76
+ <FormLabel>Title</FormLabel>
77
+ <FormControl>
78
+ <Input {...field} />
79
+ </FormControl>
80
+ <FormMessage />
81
+ </FormItem>
82
+ )}
83
+ />
84
+ </form>
85
+ </Form>
86
+ ```
87
+
88
+ ## DO
89
+
90
+ - Prefer shadcn/ui components over custom implementations for common UI patterns
91
+ - Use CSS variable customization before forking a component
92
+ - Keep `src/components/ui/` for shadcn components; use `src/shared/components/` for your own
93
+ - Update components via `shadcn@latest add <component>` when you want upstream fixes (manual review required)
94
+
95
+ ## DO NOT
96
+
97
+ - Install shadcn/ui as a package dependency — it is a code generator
98
+ - Modify shadcn components inside `node_modules`
99
+ - Build custom dialog, dropdown, or popover implementations when Radix primitives are available
100
+ - Assume accessibility — review each component in context (color contrast, label associations)
101
+
102
+ ## When to Fork a Component
103
+
104
+ Fork (copy and modify) when:
105
+ - The component needs significant structural changes
106
+ - You need different default props or variants permanently
107
+ - The upstream component API doesn't fit your use case
108
+
109
+ Do not fork to change only styles — use CSS variables instead.
110
+
111
+ ## See Also
112
+
113
+ - [`tailwind.md`](tailwind.md) — utility classes and theming
114
+ - [`../react/accessibility.md`](../react/accessibility.md) — why Radix primitives are preferred
115
+ - [`../react/forms.md`](../react/forms.md) — shadcn/ui form components
@@ -0,0 +1,135 @@
1
+ # Tailwind CSS
2
+
3
+ ## Core Rules
4
+
5
+ - Utility-first: use Tailwind classes, not custom CSS, by default
6
+ - Use CSS variables for theming — not hardcoded color values
7
+ - Minimize arbitrary values (`[value]` syntax)
8
+ - Support dark mode via CSS variables and Tailwind's `dark:` variant
9
+
10
+ ## Setup (v4)
11
+
12
+ ```bash
13
+ pnpm add tailwindcss @tailwindcss/vite
14
+ ```
15
+
16
+ ```ts
17
+ // vite.config.ts
18
+ import tailwindcss from "@tailwindcss/vite";
19
+
20
+ export default defineConfig({
21
+ plugins: [react(), tailwindcss()],
22
+ });
23
+ ```
24
+
25
+ ```css
26
+ /* src/styles/globals.css */
27
+ @import "tailwindcss";
28
+ ```
29
+
30
+ ## Theming with CSS Variables
31
+
32
+ Define design tokens as CSS variables. Wire them into Tailwind:
33
+
34
+ ```css
35
+ /* src/styles/globals.css */
36
+ @import "tailwindcss";
37
+
38
+ @theme {
39
+ --color-primary: oklch(55% 0.2 250);
40
+ --color-primary-foreground: oklch(98% 0.01 250);
41
+ --color-background: oklch(100% 0 0);
42
+ --color-foreground: oklch(10% 0 0);
43
+ --color-muted: oklch(95% 0 0);
44
+ --color-border: oklch(88% 0 0);
45
+ --radius: 0.5rem;
46
+ }
47
+
48
+ .dark {
49
+ --color-background: oklch(10% 0 0);
50
+ --color-foreground: oklch(98% 0 0);
51
+ --color-muted: oklch(18% 0 0);
52
+ --color-border: oklch(25% 0 0);
53
+ }
54
+ ```
55
+
56
+ Usage:
57
+
58
+ ```tsx
59
+ <div className="bg-background text-foreground border-border" />
60
+ ```
61
+
62
+ ## DO
63
+
64
+ - Use `cn()` (clsx + tailwind-merge) for conditional class merging
65
+ - Define component variants with `cva` (class-variance-authority)
66
+ - Use `text-foreground`, `bg-background` etc. for themeable values
67
+ - Group Tailwind classes logically: layout → spacing → typography → color → state
68
+
69
+ ## DO NOT
70
+
71
+ - Use arbitrary values for things that should be design tokens: `text-[#ff6b35]`
72
+ - Use `@apply` extensively — defeats the purpose of utility-first
73
+ - Write custom CSS for things Tailwind handles natively
74
+
75
+ ## `cn()` Helper
76
+
77
+ ```ts
78
+ // src/shared/utils/cn.ts
79
+ import { clsx, type ClassValue } from "clsx";
80
+ import { twMerge } from "tailwind-merge";
81
+
82
+ export function cn(...inputs: ClassValue[]) {
83
+ return twMerge(clsx(inputs));
84
+ }
85
+ ```
86
+
87
+ ```tsx
88
+ <button className={cn("px-4 py-2 rounded", isActive && "bg-primary text-white")} />
89
+ ```
90
+
91
+ ## Component Variants with `cva`
92
+
93
+ ```tsx
94
+ import { cva, type VariantProps } from "class-variance-authority";
95
+ import { cn } from "~/shared/utils/cn";
96
+
97
+ const buttonVariants = cva(
98
+ "inline-flex items-center justify-center rounded font-medium transition-colors",
99
+ {
100
+ variants: {
101
+ variant: {
102
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
103
+ outline: "border border-border bg-transparent hover:bg-muted",
104
+ ghost: "hover:bg-muted",
105
+ },
106
+ size: {
107
+ sm: "h-8 px-3 text-sm",
108
+ md: "h-10 px-4",
109
+ lg: "h-12 px-6 text-lg",
110
+ },
111
+ },
112
+ defaultVariants: { variant: "default", size: "md" },
113
+ },
114
+ );
115
+
116
+ interface ButtonProps
117
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
118
+ VariantProps<typeof buttonVariants> {}
119
+
120
+ export function Button({ className, variant, size, ...props }: ButtonProps) {
121
+ return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />;
122
+ }
123
+ ```
124
+
125
+ ## PRIORITY
126
+
127
+ ```
128
+ Design tokens > Utility classes > Arbitrary values > Custom CSS
129
+ ```
130
+
131
+ ## See Also
132
+
133
+ - [`shadcn.md`](shadcn.md) — Tailwind + Radix components
134
+ - [`../react/accessibility.md`](../react/accessibility.md) — focus styles, contrast
135
+ - [`../react/component-design.md`](../react/component-design.md) — `cn` and `cva` patterns
@@ -0,0 +1,214 @@
1
+ # TanStack Query
2
+
3
+ The default solution for server state management. Replaces manual `useEffect` + `useState` data fetching entirely.
4
+
5
+ ## Core Rules
6
+
7
+ - Use TanStack Query for any data that lives on the server — never duplicate it into local state
8
+ - Define `queryOptions` once and share them between loaders, components, and prefetches
9
+ - Use `useSuspenseQuery` when paired with route loaders or `<Suspense>` boundaries
10
+ - Use plain `useQuery` only when you need to render before the data resolves
11
+ - Invalidate queries on mutation success — don't manually update component state
12
+
13
+ ## Setup
14
+
15
+ ```tsx
16
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
17
+
18
+ const queryClient = new QueryClient({
19
+ defaultOptions: {
20
+ queries: {
21
+ staleTime: 60_000, // data stays fresh for 60s by default
22
+ retry: 1,
23
+ refetchOnWindowFocus: false,
24
+ },
25
+ },
26
+ });
27
+
28
+ export function Providers({ children }: { children: React.ReactNode }) {
29
+ return (
30
+ <QueryClientProvider client={queryClient}>
31
+ {children}
32
+ </QueryClientProvider>
33
+ );
34
+ }
35
+ ```
36
+
37
+ ## Query Options Pattern
38
+
39
+ Define query options as a function so the same definition works in loaders, components, and prefetches:
40
+
41
+ ```ts
42
+ import { queryOptions } from "@tanstack/react-query";
43
+
44
+ export function invoiceQueryOptions(id: string) {
45
+ return queryOptions({
46
+ queryKey: ["invoices", id],
47
+ queryFn: () => fetchInvoice(id),
48
+ staleTime: 30_000,
49
+ });
50
+ }
51
+
52
+ // Component (with route loader prefetching — see tanstack-router.md)
53
+ const { data } = useSuspenseQuery(invoiceQueryOptions(invoiceId));
54
+
55
+ // Loader prefetch
56
+ loader: ({ context }) => context.queryClient.ensureQueryData(invoiceQueryOptions(id)),
57
+
58
+ // Manual prefetch (e.g., on hover, outside a route)
59
+ queryClient.prefetchQuery(invoiceQueryOptions(id));
60
+ ```
61
+
62
+ This is the canonical pattern. Avoid defining query keys and functions inline at the call site.
63
+
64
+ ## Queries
65
+
66
+ ### Standard Query
67
+
68
+ Use when the component needs to render before data is available (initial mount without prefetch):
69
+
70
+ ```ts
71
+ const { data, isLoading, error } = useQuery({
72
+ queryKey: ["invoices", filters],
73
+ queryFn: () => fetchInvoices(filters),
74
+ });
75
+
76
+ if (error) return <ErrorState error={error} />;
77
+ if (isLoading) return <LoadingState />;
78
+ return <InvoiceTable invoices={data} />;
79
+ ```
80
+
81
+ ### Suspense Query
82
+
83
+ Use when paired with a route loader or `<Suspense>` boundary. No `isLoading` / `error` checks — boundaries handle them:
84
+
85
+ ```ts
86
+ const { data: invoices } = useSuspenseQuery(invoiceQueryOptions(filters));
87
+ return <InvoiceTable invoices={invoices} />;
88
+ ```
89
+
90
+ `<Suspense>` shows the fallback while loading. `<ErrorBoundary>` catches errors. See [`../react/error-boundaries.md`](../react/error-boundaries.md).
91
+
92
+ ### Infinite Query
93
+
94
+ For cursor-paginated lists:
95
+
96
+ ```ts
97
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
98
+ queryKey: ["invoices", filters],
99
+ queryFn: ({ pageParam }) => fetchInvoices({ ...filters, cursor: pageParam }),
100
+ initialPageParam: undefined as string | undefined,
101
+ getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
102
+ });
103
+
104
+ // data.pages is an array of pages; flatten with .flatMap(page => page.items)
105
+ ```
106
+
107
+ See [`../architecture/api-design.md`](../architecture/api-design.md) for cursor pagination conventions.
108
+
109
+ ## Mutations
110
+
111
+ ### Basic Mutation
112
+
113
+ ```ts
114
+ const { mutate, isPending } = useMutation({
115
+ mutationFn: createInvoice,
116
+ onSuccess: (newInvoice) => {
117
+ queryClient.invalidateQueries({ queryKey: ["invoices"] });
118
+ },
119
+ });
120
+ ```
121
+
122
+ ### Optimistic Updates
123
+
124
+ ```ts
125
+ const { mutate } = useMutation({
126
+ mutationFn: createInvoice,
127
+ onMutate: async (newInvoice) => {
128
+ await queryClient.cancelQueries({ queryKey: ["invoices"] });
129
+ const previous = queryClient.getQueryData<Invoice[]>(["invoices"]);
130
+ queryClient.setQueryData<Invoice[]>(["invoices"], (old = []) => [
131
+ ...old,
132
+ { ...newInvoice, id: "temp", status: "draft" },
133
+ ]);
134
+ return { previous };
135
+ },
136
+ onError: (_, __, context) => {
137
+ queryClient.setQueryData(["invoices"], context?.previous);
138
+ },
139
+ onSettled: () => {
140
+ queryClient.invalidateQueries({ queryKey: ["invoices"] });
141
+ },
142
+ });
143
+ ```
144
+
145
+ ## Query Key Conventions
146
+
147
+ Use arrays. Nest from general to specific. Filters go in a final object so they participate in cache keying:
148
+
149
+ ```ts
150
+ ["invoices"] // all invoices (broad)
151
+ ["invoices", "123"] // single invoice
152
+ ["invoices", "123", "comments"] // nested resource
153
+ ["invoices", { status: "open" }] // filtered list
154
+ ["invoices", { status: "open", page: 2 }]
155
+ ```
156
+
157
+ Centralize query keys in a `queryKeys` factory for invalidation precision:
158
+
159
+ ```ts
160
+ export const invoiceKeys = {
161
+ all: ["invoices"] as const,
162
+ lists: () => [...invoiceKeys.all, "list"] as const,
163
+ list: (filters: InvoiceFilters) => [...invoiceKeys.lists(), filters] as const,
164
+ details: () => [...invoiceKeys.all, "detail"] as const,
165
+ detail: (id: string) => [...invoiceKeys.details(), id] as const,
166
+ };
167
+
168
+ // Invalidate all lists but keep individual details fresh
169
+ queryClient.invalidateQueries({ queryKey: invoiceKeys.lists() });
170
+ ```
171
+
172
+ ## Invalidation Strategies
173
+
174
+ | After… | Invalidate… |
175
+ |---|---|
176
+ | `create` | List queries (`invoiceKeys.lists()`) |
177
+ | `update` (single resource) | The list **and** the specific detail (`invoiceKeys.detail(id)`) |
178
+ | `delete` | The list; remove the detail from cache (`queryClient.removeQueries`) |
179
+ | `bulk update` | The entire feature (`invoiceKeys.all`) |
180
+
181
+ `invalidateQueries` marks queries as stale and triggers refetch if they're active. `removeQueries` evicts them entirely.
182
+
183
+ ## `staleTime` vs `gcTime`
184
+
185
+ - **`staleTime`** — how long data is considered fresh; while fresh, no background refetch
186
+ - **`gcTime`** (formerly `cacheTime`) — how long unused data is kept in memory after the last subscriber unmounts
187
+
188
+ Sensible defaults:
189
+ - List queries: `staleTime: 30_000`
190
+ - Detail queries: `staleTime: 60_000`
191
+ - Static reference data: `staleTime: Infinity` with explicit invalidation
192
+
193
+ ## DO NOT
194
+
195
+ - Mix TanStack Query with manual `useEffect` fetch patterns
196
+ - Store server state in Zustand or Context — the query cache is the store
197
+ - Use TanStack Query for pure local UI state (modal open, selected tab without URL state)
198
+ - Define query keys inline at each call site — use `queryOptions` or a key factory
199
+ - Manually `setQueryData` for non-optimistic updates — invalidate instead
200
+
201
+ ## PRIORITY
202
+
203
+ ```
204
+ queryOptions (shared definition) > Inline query at call site
205
+ useSuspenseQuery + boundaries > useQuery + manual loading/error checks
206
+ Invalidate on mutation > Manual cache updates
207
+ ```
208
+
209
+ ## See Also
210
+
211
+ - [`tanstack-router.md`](tanstack-router.md) — loaders + `useSuspenseQuery` pattern
212
+ - [`../react/state-management.md`](../react/state-management.md) — when to use TanStack Query vs other state
213
+ - [`../react/error-boundaries.md`](../react/error-boundaries.md) — Suspense and ErrorBoundary pairing
214
+ - [`../architecture/api-design.md`](../architecture/api-design.md) — pagination, error shape