@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,139 @@
1
+ # Component Design
2
+
3
+ ## Core Rules
4
+
5
+ - One file, one primary responsibility
6
+ - Prefer feature-driven colocation over organization-by-type
7
+ - Prefer composition over prop drilling
8
+ - Avoid gigantic files — large files signal decomposition is needed
9
+
10
+ ## Feature-Driven Colocation
11
+
12
+ ```
13
+ features/
14
+ invoices/
15
+ InvoiceTable.tsx
16
+ InvoiceTable.test.tsx
17
+ InvoiceRow.tsx
18
+ InvoiceFilters.tsx
19
+ useInvoiceSort.ts
20
+ invoice.types.ts
21
+ invoice.api.ts
22
+ ```
23
+
24
+ Do not scatter invoice logic across `components/`, `hooks/`, `types/`, `api/` at the root level.
25
+
26
+ ## DO
27
+
28
+ - Keep components reasonably scoped — render one thing well
29
+ - Decompose when a file becomes hard to scan
30
+ - Use composition to avoid deep prop drilling
31
+ - Use compound components for tightly coupled UI groups
32
+ - Prefer explicit props over spreading unknown props onto DOM elements
33
+ - Forward refs with `forwardRef` when wrapping native elements
34
+
35
+ ## DO NOT
36
+
37
+ - Pass more than 4–5 props through intermediate components without reconsidering the design
38
+ - Put business logic directly in JSX — extract to a variable or function
39
+ - Use default exports for everything — named exports improve refactorability
40
+ - Render large lists inline without key management and performance consideration
41
+
42
+ ## Compound Components
43
+
44
+ Use compound components when a group of UI elements are tightly coupled and share state:
45
+
46
+ ```tsx
47
+ // Usage
48
+ <Tabs defaultValue="overview">
49
+ <Tabs.List>
50
+ <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
51
+ <Tabs.Trigger value="details">Details</Tabs.Trigger>
52
+ </Tabs.List>
53
+ <Tabs.Panel value="overview"><Overview /></Tabs.Panel>
54
+ <Tabs.Panel value="details"><Details /></Tabs.Panel>
55
+ </Tabs>
56
+ ```
57
+
58
+ ## Prop Design
59
+
60
+ Define props as a named `interface` above the component. Do not define prop types inline.
61
+
62
+ ```tsx
63
+ // GOOD — named interface above the component
64
+ interface InvoiceTableProps {
65
+ invoices: Invoice[];
66
+ onRowClick: (id: string) => void;
67
+ isLoading?: boolean;
68
+ }
69
+
70
+ export function InvoiceTable({ invoices, onRowClick, isLoading }: InvoiceTableProps) {
71
+ ...
72
+ }
73
+
74
+ // BAD — inline object type
75
+ export function InvoiceTable({
76
+ invoices,
77
+ onRowClick,
78
+ }: {
79
+ invoices: Invoice[];
80
+ onRowClick: (id: string) => void;
81
+ }) {
82
+ ...
83
+ }
84
+
85
+ // BAD — type alias when interface would do
86
+ type InvoiceTableProps = {
87
+ invoices: Invoice[];
88
+ onRowClick: (id: string) => void;
89
+ };
90
+ ```
91
+
92
+ Naming: suffix the interface with `Props` and prefix with the component name (`InvoiceTableProps`, not `Props`).
93
+
94
+ ### Variant Props
95
+
96
+ ```tsx
97
+ // BAD — boolean props for variants
98
+ <Button primary large disabled />
99
+
100
+ // GOOD — explicit variant props
101
+ <Button variant="primary" size="lg" disabled />
102
+ ```
103
+
104
+ ### Avoid Spreading Unknown Props
105
+
106
+ ```tsx
107
+ // BAD — spreading unknown props onto DOM
108
+ function Card({ className, ...rest }: unknown) {
109
+ return <div className={className} {...rest} />;
110
+ }
111
+
112
+ // GOOD — explicit allowed props
113
+ interface CardProps {
114
+ className?: string;
115
+ children: React.ReactNode;
116
+ onClick?: () => void;
117
+ }
118
+ ```
119
+
120
+ ## File Size Signals
121
+
122
+ Trigger a decomposition review when:
123
+ - A file exceeds ~300 lines
124
+ - A component handles multiple unrelated concerns
125
+ - Props grow beyond 8–10 items
126
+ - JSX exceeds what fits on a single screen
127
+
128
+ ## PRIORITY
129
+
130
+ ```
131
+ Single responsibility > Reuse > Brevity
132
+ ```
133
+
134
+ ## See Also
135
+
136
+ - [hooks.md](hooks.md)
137
+ - [state-management.md](state-management.md)
138
+ - [accessibility.md](accessibility.md)
139
+ - [anti-patterns.md](anti-patterns.md)
@@ -0,0 +1,195 @@
1
+ # Error Boundaries and Suspense
2
+
3
+ ## Core Rules
4
+
5
+ - Wrap async / risky UI subtrees in error boundaries — never let a render error crash the whole app
6
+ - Co-locate boundaries with the feature they protect
7
+ - Pair `<Suspense>` with `<ErrorBoundary>` — loading and error states are two sides of the same boundary
8
+ - Provide actionable error UI — explain what failed and what the user can do
9
+
10
+ ## Error Boundaries
11
+
12
+ React error boundaries catch errors thrown during render, in lifecycle methods, and in constructors of descendant components. They do **not** catch:
13
+
14
+ - Errors in event handlers (use try/catch or `.catch()`)
15
+ - Errors in async code (handle with TanStack Query's `onError` or `try/catch`)
16
+ - Errors in the boundary itself
17
+
18
+ Use [`react-error-boundary`](https://github.com/bvaughn/react-error-boundary) — it provides a clean hook + component API and avoids the class component boilerplate:
19
+
20
+ ```tsx
21
+ import { ErrorBoundary } from "react-error-boundary";
22
+
23
+ function InvoiceErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
24
+ return (
25
+ <div role="alert">
26
+ <p>We couldn't load your invoices. Please try again.</p>
27
+ <p><small>Error: {error.message}</small></p>
28
+ <button type="button" onClick={resetErrorBoundary}>Retry</button>
29
+ </div>
30
+ );
31
+ }
32
+
33
+ export function InvoicesPage() {
34
+ return (
35
+ <ErrorBoundary FallbackComponent={InvoiceErrorFallback}>
36
+ <InvoiceList />
37
+ </ErrorBoundary>
38
+ );
39
+ }
40
+ ```
41
+
42
+ ## Where to Place Boundaries
43
+
44
+ Place boundaries at meaningful UI seams. There are two layers:
45
+
46
+ **Route-level** — use TanStack Router's `errorComponent` and `pendingComponent` for failures and loading during route transitions (loader runs, then renders). See [`../tooling/tanstack-router.md`](../tooling/tanstack-router.md).
47
+
48
+ **Component-level** — use `<ErrorBoundary>` and `<Suspense>` for independent widgets within a page that can fail or load separately from the route as a whole.
49
+
50
+ Typical layout:
51
+ - One route-level `errorComponent` per route
52
+ - Component-level boundaries around independent widgets (dashboard panels, charts, third-party embeds)
53
+ - One component-level boundary around any subtree that suspends independently of the route loader
54
+
55
+ ```tsx
56
+ // Multiple boundaries — one failing widget doesn't kill the dashboard
57
+ function Dashboard() {
58
+ return (
59
+ <Layout>
60
+ <ErrorBoundary FallbackComponent={WidgetErrorFallback}>
61
+ <RevenueChart />
62
+ </ErrorBoundary>
63
+ <ErrorBoundary FallbackComponent={WidgetErrorFallback}>
64
+ <RecentInvoices />
65
+ </ErrorBoundary>
66
+ <ErrorBoundary FallbackComponent={WidgetErrorFallback}>
67
+ <NotificationsPanel />
68
+ </ErrorBoundary>
69
+ </Layout>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## Suspense for Loading States
75
+
76
+ `<Suspense>` declaratively handles loading states for lazy components, suspended TanStack Query calls (with `useSuspenseQuery`), and React Server Components.
77
+
78
+ ```tsx
79
+ import { Suspense, lazy } from "react";
80
+
81
+ const InvoiceTable = lazy(() => import("./InvoiceTable"));
82
+
83
+ export function InvoicesPage() {
84
+ return (
85
+ <ErrorBoundary FallbackComponent={InvoiceErrorFallback}>
86
+ <Suspense fallback={<TableSkeleton />}>
87
+ <InvoiceTable />
88
+ </Suspense>
89
+ </ErrorBoundary>
90
+ );
91
+ }
92
+ ```
93
+
94
+ ## Pairing Error and Loading Boundaries
95
+
96
+ Loading + error are two sides of the same boundary. Always declare them together:
97
+
98
+ ```tsx
99
+ <ErrorBoundary FallbackComponent={ErrorFallback}>
100
+ <Suspense fallback={<LoadingFallback />}>
101
+ <FeatureContent />
102
+ </Suspense>
103
+ </ErrorBoundary>
104
+ ```
105
+
106
+ ## Suspense with TanStack Query
107
+
108
+ ```tsx
109
+ import { useSuspenseQuery } from "@tanstack/react-query";
110
+
111
+ function InvoiceTable() {
112
+ // No isLoading / isError branching — the boundaries handle it
113
+ const { data: invoices } = useSuspenseQuery({
114
+ queryKey: ["invoices"],
115
+ queryFn: fetchInvoices,
116
+ });
117
+
118
+ return <table>...</table>;
119
+ }
120
+ ```
121
+
122
+ ## Loading and Empty States
123
+
124
+ A complete data-fetching UI has four states:
125
+
126
+ - **Loading** — handled by `<Suspense>` or `isLoading` flag
127
+ - **Error** — handled by `<ErrorBoundary>` or `error` flag
128
+ - **Empty** — no data found; show a helpful empty state, not a blank screen
129
+ - **Success** — render the data
130
+
131
+ ```tsx
132
+ if (invoices.length === 0) {
133
+ return (
134
+ <div>
135
+ <p>No invoices yet.</p>
136
+ <button type="button" onClick={onCreate}>Create your first invoice</button>
137
+ </div>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ## Skeleton Loaders
143
+
144
+ For above-the-fold content, use skeleton loaders (placeholder shapes) instead of spinners — they reduce perceived load time and prevent layout shift.
145
+
146
+ ```tsx
147
+ function TableSkeleton() {
148
+ return (
149
+ <div aria-busy="true" aria-label="Loading invoices">
150
+ {Array.from({ length: 5 }).map((_, i) => (
151
+ <div key={i} className="h-12 bg-muted animate-pulse rounded mb-2" />
152
+ ))}
153
+ </div>
154
+ );
155
+ }
156
+ ```
157
+
158
+ ## Reporting Errors
159
+
160
+ In an `<ErrorBoundary>`, send caught errors to your error monitoring service:
161
+
162
+ ```tsx
163
+ <ErrorBoundary
164
+ FallbackComponent={InvoiceErrorFallback}
165
+ onError={(error, info) => {
166
+ reportError(error, { componentStack: info.componentStack });
167
+ }}
168
+ >
169
+ <InvoiceList />
170
+ </ErrorBoundary>
171
+ ```
172
+
173
+ See [`tooling/observability.md`](../tooling/observability.md) for error monitoring patterns.
174
+
175
+ ## DO NOT
176
+
177
+ - Wrap the entire app in a single error boundary — a leaf failure should not blank the whole UI
178
+ - Show a generic "Something went wrong" message — give specific, actionable feedback
179
+ - Swallow the error in the fallback without reporting it
180
+ - Use `<Suspense>` for things that don't actually suspend (regular `useState`, non-suspended queries)
181
+
182
+ ## PRIORITY
183
+
184
+ ```
185
+ Co-located boundaries > Single global boundary
186
+ Specific error UI > Generic fallback
187
+ Suspense + ErrorBoundary together > Either alone
188
+ Route-level errorComponent (route transitions) > Component-level ErrorBoundary (widgets)
189
+ ```
190
+
191
+ ## See Also
192
+
193
+ - [`../tooling/tanstack-router.md`](../tooling/tanstack-router.md) — route-level `pendingComponent` / `errorComponent`
194
+ - [`../tooling/tanstack-query.md`](../tooling/tanstack-query.md) — `useSuspenseQuery` for component-level boundaries
195
+ - [`../tooling/observability.md`](../tooling/observability.md) — reporting caught errors to monitoring
@@ -0,0 +1,147 @@
1
+ # Forms
2
+
3
+ ## Core Rules
4
+
5
+ - Prefer controlled inputs
6
+ - Validate with a shared Zod schema (used on both client and server)
7
+ - Use optimistic updates for responsive UIs
8
+ - Handle loading and error states explicitly
9
+
10
+ ## Preferred Tooling
11
+
12
+ **TanStack Form** is the preferred choice — integrates with TanStack Query and supports schema validation natively.
13
+
14
+ **React Hook Form** is an acceptable alternative — mature, well-documented, minimal re-renders.
15
+
16
+ Do not build custom form state management with `useState` arrays.
17
+
18
+ ## TanStack Form Pattern
19
+
20
+ ```tsx
21
+ import { useForm } from "@tanstack/react-form";
22
+ import { zodValidator } from "@tanstack/zod-form-adapter";
23
+ import { createInvoiceSchema } from "~/features/invoices/invoice.schema";
24
+
25
+ function CreateInvoiceForm() {
26
+ const form = useForm({
27
+ defaultValues: { title: "", amount: 0 },
28
+ validatorAdapter: zodValidator(),
29
+ validators: {
30
+ onChange: createInvoiceSchema,
31
+ },
32
+ onSubmit: async ({ value }) => {
33
+ await createInvoice(value);
34
+ },
35
+ });
36
+
37
+ return (
38
+ <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
39
+ <form.Field
40
+ name="title"
41
+ children={(field) => (
42
+ <label>
43
+ Title
44
+ <input
45
+ value={field.state.value}
46
+ onChange={(e) => field.handleChange(e.target.value)}
47
+ />
48
+ {field.state.meta.errors.map((error) => (
49
+ <span key={error} role="alert">{error}</span>
50
+ ))}
51
+ </label>
52
+ )}
53
+ />
54
+ <button type="submit">Create</button>
55
+ </form>
56
+ );
57
+ }
58
+ ```
59
+
60
+ ## React Hook Form Pattern
61
+
62
+ ```tsx
63
+ import { useForm } from "react-hook-form";
64
+ import { zodResolver } from "@hookform/resolvers/zod";
65
+ import { type CreateInvoice, createInvoiceSchema } from "~/features/invoices/invoice.schema";
66
+
67
+ function CreateInvoiceForm() {
68
+ const {
69
+ register,
70
+ handleSubmit,
71
+ formState: { errors, isSubmitting },
72
+ } = useForm<CreateInvoice>({
73
+ resolver: zodResolver(createInvoiceSchema),
74
+ });
75
+
76
+ const onSubmit = async (data: CreateInvoice) => {
77
+ await createInvoice(data);
78
+ };
79
+
80
+ return (
81
+ <form onSubmit={handleSubmit(onSubmit)}>
82
+ <label htmlFor="title">Title</label>
83
+ <input id="title" {...register("title")} />
84
+ {errors.title && <span role="alert">{errors.title.message}</span>}
85
+ <button type="submit" disabled={isSubmitting}>Create</button>
86
+ </form>
87
+ );
88
+ }
89
+ ```
90
+
91
+ ## Shared Schema
92
+
93
+ ```ts
94
+ // features/invoices/invoice.schema.ts
95
+ import { z } from "zod";
96
+
97
+ export const createInvoiceSchema = z.object({
98
+ title: z.string().min(1, "Title is required").max(200),
99
+ amount: z.number().positive("Amount must be positive"),
100
+ dueDate: z.coerce.date().min(new Date(), "Due date must be in the future"),
101
+ });
102
+
103
+ export type CreateInvoice = z.infer<typeof createInvoiceSchema>;
104
+ ```
105
+
106
+ ## Optimistic Updates
107
+
108
+ ```ts
109
+ const { mutate } = useMutation({
110
+ mutationFn: createInvoice,
111
+ onMutate: async (newInvoice) => {
112
+ await queryClient.cancelQueries({ queryKey: ["invoices"] });
113
+ const previous = queryClient.getQueryData(["invoices"]);
114
+ queryClient.setQueryData(["invoices"], (old: Invoice[]) => [
115
+ ...old,
116
+ { ...newInvoice, id: "temp", status: "pending" },
117
+ ]);
118
+ return { previous };
119
+ },
120
+ onError: (_, __, context) => {
121
+ queryClient.setQueryData(["invoices"], context?.previous);
122
+ },
123
+ onSettled: () => {
124
+ queryClient.invalidateQueries({ queryKey: ["invoices"] });
125
+ },
126
+ });
127
+ ```
128
+
129
+ ## DO NOT
130
+
131
+ - Build form state with multiple `useState` calls
132
+ - Duplicate validation logic between client and server
133
+ - Submit without disabling the submit button during submission
134
+ - Leave error states unhandled
135
+
136
+ ## PRIORITY
137
+
138
+ ```
139
+ Schema validation > Controlled inputs > Optimistic updates
140
+ ```
141
+
142
+ ## See Also
143
+
144
+ - [`../typescript/validation.md`](../typescript/validation.md) — Zod schema patterns
145
+ - [`accessibility.md`](accessibility.md) — labels, errors, keyboard support
146
+ - [`../tooling/tanstack-query.md`](../tooling/tanstack-query.md) — mutations and cache invalidation
147
+ - [`../tooling/shadcn.md`](../tooling/shadcn.md) — shadcn/ui form components
@@ -0,0 +1,88 @@
1
+ # Hooks
2
+
3
+ ## Core Rules
4
+
5
+ - Use hooks to encapsulate reusable stateful behavior
6
+ - Keep hooks composable — hooks should call other hooks, not re-implement logic
7
+ - Before writing a custom hook, check if a solution already exists
8
+
9
+ ## Check First
10
+
11
+ Before writing a custom hook:
12
+
13
+ 1. Check [`@uidotdev/usehooks`](https://usehooks.com/) — covers most common patterns
14
+ 2. Check if TanStack Query, TanStack Router, or Zustand handles your case
15
+ 3. Check if a plain utility function (non-hook) is sufficient
16
+
17
+ ## DO
18
+
19
+ - Name hooks with the `use` prefix
20
+ - Keep hooks focused on a single behavior
21
+ - Compose hooks from smaller hooks
22
+ - Return only what the caller needs — avoid returning large objects when specific values suffice
23
+ - Expose stable references with `useCallback` when callbacks are drilled or passed to external APIs
24
+
25
+ ## DO NOT
26
+
27
+ - Create hook mini-frameworks with complex internal state machines
28
+ - Wrap `useState` purely to rename it — that is indirection, not abstraction
29
+ - Hide side effects or subscriptions without documenting them
30
+ - Create hooks that only one component ever calls (unless the hook isolates complexity)
31
+ - Use hooks to share instance state between unrelated components — use context or a store instead
32
+
33
+ ## Good Hook Shape
34
+
35
+ ```ts
36
+ // Focused, composable, clear return
37
+ function useDisclosure(defaultOpen = false) {
38
+ const [isOpen, setIsOpen] = useState(defaultOpen);
39
+ const open = useCallback(() => setIsOpen(true), []);
40
+ const close = useCallback(() => setIsOpen(false), []);
41
+ const toggle = useCallback(() => setIsOpen((prev) => !prev), []);
42
+ return { isOpen, open, close, toggle };
43
+ }
44
+ ```
45
+
46
+ ## Bad Hook Shape
47
+
48
+ ```ts
49
+ // BAD — wraps useState purely for renaming
50
+ function useModalOpen() {
51
+ const [isOpen, setIsOpen] = useState(false);
52
+ return [isOpen, setIsOpen] as const;
53
+ }
54
+
55
+ // BAD — hidden side effect with no documentation
56
+ function useUserData(userId: string) {
57
+ const [data, setData] = useState(null);
58
+ useEffect(() => {
59
+ // secretly sends analytics on every render
60
+ track("user-viewed");
61
+ fetchUser(userId).then(setData);
62
+ }, [userId]);
63
+ return data;
64
+ }
65
+ ```
66
+
67
+ ## Composing Hooks
68
+
69
+ ```ts
70
+ function useInvoiceTable(filters: InvoiceFilters) {
71
+ const { data, isLoading } = useInvoices(filters); // data fetching
72
+ const { sorted, sortKey, setSortKey } = useSort(data); // sort behavior
73
+ const { paginated, page, setPage } = usePagination(sorted); // pagination
74
+ return { invoices: paginated, isLoading, sortKey, setSortKey, page, setPage };
75
+ }
76
+ ```
77
+
78
+ ## PRIORITY
79
+
80
+ ```
81
+ Composability > Encapsulation > Abstraction
82
+ ```
83
+
84
+ ## See Also
85
+
86
+ - [use-effect.md](use-effect.md)
87
+ - [memoization.md](memoization.md)
88
+ - [state-management.md](state-management.md)
@@ -0,0 +1,87 @@
1
+ # Memoization
2
+
3
+ ## Core Rule
4
+
5
+ Memoize proven performance bottlenecks. Do not memoize speculatively.
6
+
7
+ ## DO
8
+
9
+ - Use `useCallback` for callbacks passed to heavily optimized child components or external APIs that need stable references
10
+ - Use `useMemo` for computationally expensive operations that have been profiled
11
+ - Use `React.memo` on components that receive stable props and re-render frequently with identical inputs
12
+
13
+ ## DO NOT
14
+
15
+ - Wrap every function in `useCallback` by default
16
+ - Wrap every value in `useMemo` by default
17
+ - Use memoization to silence exhaustive-deps lint warnings
18
+ - Use `React.memo` on every component
19
+ - Reach for memoization before profiling
20
+
21
+ ## The Cost of Memoization
22
+
23
+ Every `useMemo` and `useCallback` adds:
24
+ - Memory overhead (the cached value + the dependency array)
25
+ - Comparison overhead (shallow equality check on every render)
26
+ - Cognitive overhead (the reader must understand what is being memoized and why)
27
+
28
+ These costs are only worth paying when the avoided re-render or recalculation is measurably expensive.
29
+
30
+ ## When `useCallback` Makes Sense
31
+
32
+ ```tsx
33
+ // GOOD — stable callback passed to a memoized child
34
+ const handleRowClick = useCallback((id: string) => {
35
+ navigate(`/invoices/${id}`);
36
+ }, [navigate]);
37
+
38
+ return <InvoiceTable onRowClick={handleRowClick} />;
39
+ ```
40
+
41
+ ## When `useMemo` Makes Sense
42
+
43
+ ```tsx
44
+ // GOOD — expensive computation, profiled
45
+ const sortedAndFilteredInvoices = useMemo(() => {
46
+ return invoices
47
+ .filter(matchesFilters)
48
+ .sort(bySortKey(sortKey));
49
+ }, [invoices, matchesFilters, sortKey]);
50
+ ```
51
+
52
+ ## When Memoization Is Unnecessary
53
+
54
+ ```tsx
55
+ // BAD — trivial computation, memoization is noise
56
+ const label = useMemo(() => `Hello, ${name}`, [name]);
57
+
58
+ // GOOD
59
+ const label = `Hello, ${name}`;
60
+ ```
61
+
62
+ ```tsx
63
+ // BAD — callback not passed anywhere that needs stability
64
+ const handleClick = useCallback(() => setOpen(true), []);
65
+
66
+ // GOOD
67
+ const handleClick = () => setOpen(true);
68
+ ```
69
+
70
+ ## Profiling First
71
+
72
+ Before adding memoization, use React DevTools Profiler to confirm:
73
+ 1. The component is re-rendering
74
+ 2. The re-render is caused by the thing you are memoizing
75
+ 3. The re-render is causing a measurable performance problem
76
+
77
+ ## PRIORITY
78
+
79
+ ```
80
+ Readability > Micro-optimization
81
+ ```
82
+
83
+ ## See Also
84
+
85
+ - [`component-design.md`](component-design.md) — decomposition over memoization
86
+ - [`anti-patterns.md`](anti-patterns.md) — defensive memoization
87
+ - [`../tooling/observability.md`](../tooling/observability.md) — Web Vitals for real performance signals