@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,365 @@
1
+ # TanStack Router
2
+
3
+ ## Core Rules
4
+
5
+ - Define routes as code (or file-based) with full type inference — no string-typed paths
6
+ - Validate all search params with Zod — typed and runtime-safe
7
+ - Load data in route loaders, consume with `useSuspenseQuery` — eliminates loading flicker on navigation
8
+ - Use route-level `pendingComponent` / `errorComponent` for navigation transitions; component-level `<Suspense>` / `<ErrorBoundary>` for in-page widgets
9
+ - Auth-guard with `beforeLoad` — redirect before the route renders, never inside the component
10
+ - Treat the URL as the source of truth for filters, search, pagination, and tab state
11
+
12
+ ## Why Router + Query
13
+
14
+ TanStack Router and TanStack Query together change how data fetching is structured:
15
+
16
+ - **Loader prefetches data** before the component mounts
17
+ - **Component reads from cache** via `useSuspenseQuery` — synchronously returns cached data
18
+ - **No loading state on navigation** — the data is already there when the component renders
19
+ - **Single source of truth** — the query cache serves both loaders and components
20
+
21
+ The pattern flips the usual "fetch inside the component" model.
22
+
23
+ ## Route Definition
24
+
25
+ ### Code-Based Routes
26
+
27
+ Define routes as a tree of `createRoute` calls. Best for full type inference and explicit composition.
28
+
29
+ ```ts
30
+ import { createRootRoute, createRoute, createRouter } from "@tanstack/react-router";
31
+
32
+ const rootRoute = createRootRoute({ component: RootLayout });
33
+
34
+ const invoicesRoute = createRoute({
35
+ getParentRoute: () => rootRoute,
36
+ path: "/invoices",
37
+ component: InvoicesPage,
38
+ });
39
+
40
+ const invoiceDetailRoute = createRoute({
41
+ getParentRoute: () => invoicesRoute,
42
+ path: "$invoiceId",
43
+ component: InvoiceDetailPage,
44
+ });
45
+
46
+ const routeTree = rootRoute.addChildren([invoicesRoute, invoiceDetailRoute]);
47
+
48
+ export const router = createRouter({ routeTree });
49
+
50
+ declare module "@tanstack/react-router" {
51
+ interface Register {
52
+ router: typeof router;
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### File-Based Routes
58
+
59
+ For larger apps, file-based routing reduces ceremony. Routes live in `src/routes/` and the generator creates the route tree:
60
+
61
+ ```
62
+ src/routes/
63
+ __root.tsx ← root layout
64
+ index.tsx ← /
65
+ invoices/
66
+ index.tsx ← /invoices
67
+ $invoiceId.tsx ← /invoices/:invoiceId
68
+ _layout.tsx ← shared layout for /invoices/*
69
+ ```
70
+
71
+ Use the `@tanstack/router-plugin/vite` plugin to regenerate the route tree on save.
72
+
73
+ **When to choose which:** code-based for libraries, embedded apps, or small projects; file-based for production apps with many routes.
74
+
75
+ ## Typed Path Params
76
+
77
+ Path params are inferred from the path string:
78
+
79
+ ```ts
80
+ const invoiceDetailRoute = createRoute({
81
+ getParentRoute: () => invoicesRoute,
82
+ path: "$invoiceId",
83
+ component: InvoiceDetailPage,
84
+ });
85
+
86
+ function InvoiceDetailPage() {
87
+ const { invoiceId } = invoiceDetailRoute.useParams(); // string, fully typed
88
+ return <InvoiceDetail id={invoiceId} />;
89
+ }
90
+ ```
91
+
92
+ For runtime-validated params (e.g., must be a CUID), use `parseParams`:
93
+
94
+ ```ts
95
+ parseParams: ({ invoiceId }) => ({ invoiceId: z.string().cuid().parse(invoiceId) }),
96
+ ```
97
+
98
+ ## Typed Search Params
99
+
100
+ Search params are the canonical place for filters, search queries, pagination, and tab selection — anywhere the state should be shareable, bookmarkable, and survive a refresh.
101
+
102
+ ```ts
103
+ const invoicesRoute = createRoute({
104
+ getParentRoute: () => rootRoute,
105
+ path: "/invoices",
106
+ validateSearch: z.object({
107
+ status: z.enum(["all", "open", "paid"]).default("all"),
108
+ page: z.number().int().positive().default(1),
109
+ sort: z.enum(["dueDate", "amount"]).default("dueDate"),
110
+ }),
111
+ component: InvoicesPage,
112
+ });
113
+
114
+ function InvoicesPage() {
115
+ const { status, page, sort } = invoicesRoute.useSearch();
116
+ const navigate = invoicesRoute.useNavigate();
117
+
118
+ function setStatus(status: InvoiceStatus) {
119
+ navigate({ search: (prev) => ({ ...prev, status, page: 1 }) });
120
+ }
121
+
122
+ return <InvoiceTable status={status} page={page} sort={sort} onStatusChange={setStatus} />;
123
+ }
124
+ ```
125
+
126
+ Search params are validated on every navigation. Invalid values are coerced or rejected by the schema.
127
+
128
+ ## Loaders + TanStack Query (The Core Pattern)
129
+
130
+ The recommended pattern: loaders prefetch with `queryClient.ensureQueryData`, components consume with `useSuspenseQuery`.
131
+
132
+ ```ts
133
+ // 1. Define the query options outside the route so loader + component share them
134
+ function invoicesQueryOptions(filters: InvoicesSearch) {
135
+ return queryOptions({
136
+ queryKey: ["invoices", filters],
137
+ queryFn: () => fetchInvoices(filters),
138
+ staleTime: 30_000,
139
+ });
140
+ }
141
+
142
+ // 2. The loader prefetches into the query cache
143
+ const invoicesRoute = createRoute({
144
+ getParentRoute: () => rootRoute,
145
+ path: "/invoices",
146
+ validateSearch: invoicesSearchSchema,
147
+ loaderDeps: ({ search }) => search,
148
+ loader: ({ context, deps }) =>
149
+ context.queryClient.ensureQueryData(invoicesQueryOptions(deps)),
150
+ component: InvoicesPage,
151
+ });
152
+
153
+ // 3. The component reads from the cache — synchronous, no loading state
154
+ function InvoicesPage() {
155
+ const search = invoicesRoute.useSearch();
156
+ const { data: invoices } = useSuspenseQuery(invoicesQueryOptions(search));
157
+ return <InvoiceTable invoices={invoices} />;
158
+ }
159
+ ```
160
+
161
+ What this gives you:
162
+ - **No loading flicker on navigation** — data is in cache before render
163
+ - **Stale-while-revalidate** — TanStack Query refetches in the background if stale
164
+ - **Cache reuse** — going back to the page hits the cache instantly
165
+ - **Prefetching** — `<Link preload="intent">` prefetches on hover
166
+
167
+ ### Router Setup with QueryClient in Context
168
+
169
+ Pass the `queryClient` to the router so loaders can use it:
170
+
171
+ ```tsx
172
+ const queryClient = new QueryClient({ /* ... */ });
173
+
174
+ const router = createRouter({
175
+ routeTree,
176
+ context: { queryClient },
177
+ defaultPreload: "intent",
178
+ });
179
+
180
+ export function App() {
181
+ return (
182
+ <QueryClientProvider client={queryClient}>
183
+ <RouterProvider router={router} />
184
+ </QueryClientProvider>
185
+ );
186
+ }
187
+ ```
188
+
189
+ ## Pending and Error Components
190
+
191
+ Route-level components handle the boundary between routes. Different from in-page widgets.
192
+
193
+ ```ts
194
+ const invoicesRoute = createRoute({
195
+ getParentRoute: () => rootRoute,
196
+ path: "/invoices",
197
+ loader: /* ... */,
198
+ pendingComponent: InvoicesPending,
199
+ errorComponent: InvoicesError,
200
+ component: InvoicesPage,
201
+ });
202
+
203
+ function InvoicesPending() {
204
+ return <TableSkeleton />;
205
+ }
206
+
207
+ function InvoicesError({ error }: { error: Error }) {
208
+ return (
209
+ <div role="alert">
210
+ <p>We couldn't load invoices.</p>
211
+ <p><small>{error.message}</small></p>
212
+ </div>
213
+ );
214
+ }
215
+ ```
216
+
217
+ **Use route-level `pendingComponent` / `errorComponent`** for the entire route transition (loader runs, then renders).
218
+ **Use component-level `<Suspense>` / `<ErrorBoundary>`** for independent widgets within the page that can fail or load separately (see [`../react/error-boundaries.md`](../react/error-boundaries.md)).
219
+
220
+ ## Auth Guards with `beforeLoad`
221
+
222
+ `beforeLoad` runs before the loader. It is the right place for authentication and authorization:
223
+
224
+ ```ts
225
+ const dashboardRoute = createRoute({
226
+ getParentRoute: () => rootRoute,
227
+ path: "/dashboard",
228
+ beforeLoad: ({ context, location }) => {
229
+ if (!context.auth.isAuthenticated) {
230
+ throw redirect({
231
+ to: "/login",
232
+ search: { returnTo: location.href },
233
+ });
234
+ }
235
+ },
236
+ loader: /* ... */,
237
+ component: DashboardPage,
238
+ });
239
+ ```
240
+
241
+ `throw redirect(...)` short-circuits the route — the component never renders.
242
+
243
+ Pass `auth` through the router context (same pattern as `queryClient`).
244
+
245
+ ## Code Splitting
246
+
247
+ Lazy-load route components to reduce initial bundle size:
248
+
249
+ ```ts
250
+ const invoicesRoute = createRoute({
251
+ getParentRoute: () => rootRoute,
252
+ path: "/invoices",
253
+ component: lazyRouteComponent(() => import("./InvoicesPage")),
254
+ });
255
+ ```
256
+
257
+ For file-based routes, prefix the file with `_` to make it lazy:
258
+
259
+ ```
260
+ src/routes/invoices.lazy.tsx ← lazy-loaded
261
+ ```
262
+
263
+ ## Route Context
264
+
265
+ Share data down the route tree without prop drilling:
266
+
267
+ ```ts
268
+ const router = createRouter({
269
+ routeTree,
270
+ context: {
271
+ queryClient,
272
+ auth: undefined!, // filled in by the auth route
273
+ },
274
+ });
275
+
276
+ const authRoute = createRoute({
277
+ getParentRoute: () => rootRoute,
278
+ beforeLoad: ({ context }) => ({
279
+ auth: { user: getCurrentUser(), isAuthenticated: !!getCurrentUser() },
280
+ }),
281
+ });
282
+ ```
283
+
284
+ Child routes inherit and can extend the context.
285
+
286
+ ## Type-Safe Navigation
287
+
288
+ `Link`, `navigate`, and `redirect` are all typed against the route tree:
289
+
290
+ ```tsx
291
+ import { Link } from "@tanstack/react-router";
292
+
293
+ // Path and params are fully typed — typos and missing params are compile errors
294
+ <Link to="/invoices/$invoiceId" params={{ invoiceId: invoice.id }}>
295
+ View invoice
296
+ </Link>
297
+
298
+ // Search params are typed against the route's validateSearch schema
299
+ <Link
300
+ to="/invoices"
301
+ search={{ status: "open", page: 1 }}
302
+ >
303
+ Open invoices
304
+ </Link>
305
+
306
+ // Preload on hover / focus
307
+ <Link to="/invoices/$invoiceId" params={{ invoiceId }} preload="intent">
308
+ View
309
+ </Link>
310
+ ```
311
+
312
+ Programmatic navigation:
313
+
314
+ ```ts
315
+ const navigate = useNavigate();
316
+ navigate({ to: "/invoices/$invoiceId", params: { invoiceId } });
317
+ ```
318
+
319
+ ## Invalidation After Mutations
320
+
321
+ When a mutation changes data, invalidate the relevant queries and optionally navigate:
322
+
323
+ ```tsx
324
+ const { mutate } = useMutation({
325
+ mutationFn: createInvoice,
326
+ onSuccess: async (newInvoice) => {
327
+ await queryClient.invalidateQueries({ queryKey: ["invoices"] });
328
+ navigate({ to: "/invoices/$invoiceId", params: { invoiceId: newInvoice.id } });
329
+ },
330
+ });
331
+ ```
332
+
333
+ For server-driven navigation (e.g., after delete, go back to the list):
334
+
335
+ ```ts
336
+ onSuccess: () => {
337
+ queryClient.invalidateQueries({ queryKey: ["invoices"] });
338
+ navigate({ to: "/invoices", search: { status: "all", page: 1 } });
339
+ };
340
+ ```
341
+
342
+ ## DO NOT
343
+
344
+ - Define routes with stringly-typed paths — use `createRoute` or file-based routing for type inference
345
+ - Fetch data inside the component on mount — use the loader
346
+ - Use `useEffect` to read URL params — use `useSearch` / `useParams`
347
+ - Put search-param logic in component state and sync it to the URL — use the router's `validateSearch`
348
+ - Skip `validateSearch` "just for now" — invalid params will leak into your queries and cause cache pollution
349
+ - Build custom auth-redirect logic inside components — use `beforeLoad`
350
+
351
+ ## PRIORITY
352
+
353
+ ```
354
+ Loader-prefetched + Suspense queries > Component-level fetching
355
+ Typed search params > Component state for shareable values
356
+ beforeLoad redirects > Component-level redirects
357
+ Route errorComponent > Component-level ErrorBoundary (for route-scoped errors)
358
+ ```
359
+
360
+ ## See Also
361
+
362
+ - [`tanstack-query.md`](tanstack-query.md) — query options, mutations, invalidation
363
+ - [`../react/state-management.md`](../react/state-management.md) — URL state at the top of the hierarchy
364
+ - [`../react/error-boundaries.md`](../react/error-boundaries.md) — component-level error and loading boundaries
365
+ - [`../architecture/api-design.md`](../architecture/api-design.md) — API client and pagination patterns
@@ -0,0 +1,31 @@
1
+ # TanStack
2
+
3
+ The TanStack family is the preferred toolkit for routing, server state, and forms.
4
+
5
+ | Library | Doc | Purpose |
6
+ |---|---|---|
7
+ | TanStack Router | [`tanstack-router.md`](tanstack-router.md) | Type-safe routing, loaders, typed search params |
8
+ | TanStack Query | [`tanstack-query.md`](tanstack-query.md) | Server state, caching, mutations |
9
+ | TanStack Form | [`../react/forms.md`](../react/forms.md) | Type-safe form state with schema validation |
10
+
11
+ ## Why TanStack
12
+
13
+ - Each library is independent but composes cleanly with the others
14
+ - Type inference flows end-to-end — fewer hand-typed DTOs
15
+ - Schema validation (Zod) provides a consistent backbone across router, query, and form
16
+ - Router + Query together eliminate loading flicker on navigation — see [`tanstack-router.md`](tanstack-router.md)
17
+
18
+ ## When All Three Compose
19
+
20
+ A typical data flow uses all three:
21
+
22
+ 1. **Router** loads data in a `loader` via `queryClient.ensureQueryData`
23
+ 2. **Query** caches it; components consume it via `useSuspenseQuery`
24
+ 3. **Form** mutations call `queryClient.invalidateQueries` after submission
25
+ 4. The Router navigates to the next route, which may already be prefetched
26
+
27
+ ## See Also
28
+
29
+ - [`../react/state-management.md`](../react/state-management.md) — state hierarchy (URL > server > local > context)
30
+ - [`../react/forms.md`](../react/forms.md) — TanStack Form patterns
31
+ - [`../architecture/api-design.md`](../architecture/api-design.md) — API contracts that feed these libraries
@@ -0,0 +1,135 @@
1
+ # Vite
2
+
3
+ ## Purpose
4
+
5
+ Vite is the preferred build tool for React applications and libraries. It provides fast dev-server HMR and optimized production builds.
6
+
7
+ ## Recommended Config
8
+
9
+ ```ts
10
+ // vite.config.ts
11
+ import { defineConfig } from "vite";
12
+ import react from "@vitejs/plugin-react";
13
+ import tsconfigPaths from "vite-tsconfig-paths";
14
+
15
+ export default defineConfig({
16
+ plugins: [
17
+ react(),
18
+ tsconfigPaths(), // resolves ~/... aliases from tsconfig paths
19
+ ],
20
+ build: {
21
+ target: "es2022",
22
+ sourcemap: true,
23
+ },
24
+ server: {
25
+ port: 3000,
26
+ },
27
+ });
28
+ ```
29
+
30
+ ## Path Aliases
31
+
32
+ Use `~/` for absolute imports. Configure in `tsconfig.json` and resolve with `vite-tsconfig-paths`:
33
+
34
+ ```json
35
+ // tsconfig.json
36
+ {
37
+ "compilerOptions": {
38
+ "paths": {
39
+ "~/*": ["./src/*"]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ```ts
46
+ // Usage
47
+ import { InvoiceTable } from "~/features/invoices/InvoiceTable";
48
+ import { formatCurrency } from "~/shared/utils/format-currency";
49
+ ```
50
+
51
+ ## Environment Variables
52
+
53
+ Vite exposes env vars prefixed with `VITE_` to the client:
54
+
55
+ ```
56
+ VITE_API_BASE_URL=https://api.example.com
57
+ ```
58
+
59
+ ```ts
60
+ // Access in code
61
+ const apiBase = import.meta.env.VITE_API_BASE_URL;
62
+ ```
63
+
64
+ Validate env vars at startup:
65
+
66
+ ```ts
67
+ // src/lib/env.ts
68
+ import { z } from "zod";
69
+
70
+ const envSchema = z.object({
71
+ VITE_API_BASE_URL: z.string().url(),
72
+ });
73
+
74
+ export const env = envSchema.parse(import.meta.env);
75
+ ```
76
+
77
+ ## Vitest Integration
78
+
79
+ Vitest runs inside Vite — same config, same transforms:
80
+
81
+ ```ts
82
+ // vite.config.ts
83
+ import { defineConfig } from "vite";
84
+
85
+ export default defineConfig({
86
+ plugins: [react(), tsconfigPaths()],
87
+ test: {
88
+ environment: "jsdom",
89
+ setupFiles: ["./src/test/setup.ts"],
90
+ globals: true,
91
+ },
92
+ });
93
+ ```
94
+
95
+ ```ts
96
+ // src/test/setup.ts
97
+ import "@testing-library/jest-dom";
98
+ ```
99
+
100
+ ## Code Splitting
101
+
102
+ Vite splits by dynamic import automatically:
103
+
104
+ ```ts
105
+ const InvoicePage = lazy(() => import("~/features/invoices/InvoicePage"));
106
+ ```
107
+
108
+ For manual chunk control:
109
+
110
+ ```ts
111
+ // vite.config.ts
112
+ build: {
113
+ rollupOptions: {
114
+ output: {
115
+ manualChunks: {
116
+ vendor: ["react", "react-dom"],
117
+ tanstack: ["@tanstack/react-query", "@tanstack/react-router"],
118
+ },
119
+ },
120
+ },
121
+ },
122
+ ```
123
+
124
+ ## DO NOT
125
+
126
+ - Use `process.env` in Vite client code — use `import.meta.env`
127
+ - Commit `.env` files with secrets — use `.env.local` (gitignored)
128
+ - Disable sourcemaps in production without good reason
129
+
130
+ ## See Also
131
+
132
+ - [`biome.md`](biome.md) — lint and format
133
+ - [`dependencies.md`](dependencies.md) — bundle impact tools
134
+ - [`../security/secrets.md`](../security/secrets.md) — env var validation
135
+ - [`../react/testing.md`](../react/testing.md) — Vitest runs inside Vite
@@ -0,0 +1,107 @@
1
+ # TypeScript Anti-Patterns
2
+
3
+ ## `any`
4
+
5
+ ```ts
6
+ // BAD — erases all type safety
7
+ function process(data: any) {
8
+ return data.value;
9
+ }
10
+
11
+ // GOOD — use unknown and narrow
12
+ function process(data: unknown) {
13
+ if (typeof data === "object" && data !== null && "value" in data) {
14
+ return data.value;
15
+ }
16
+ throw new Error("Unexpected data shape");
17
+ }
18
+ ```
19
+
20
+ ## Type Assertions
21
+
22
+ ```ts
23
+ // BAD — silences the compiler without verification
24
+ const user = JSON.parse(raw) as User;
25
+
26
+ // GOOD — validate before using
27
+ const user = userSchema.parse(JSON.parse(raw));
28
+ ```
29
+
30
+ ## Enums
31
+
32
+ ```ts
33
+ // BAD — compiled JS artifact, not tree-shakeable, verbose
34
+ enum Status {
35
+ Active = "ACTIVE",
36
+ Inactive = "INACTIVE",
37
+ }
38
+
39
+ // GOOD — plain union, zero runtime cost
40
+ type Status = "ACTIVE" | "INACTIVE";
41
+ ```
42
+
43
+ ## DTO Duplication
44
+
45
+ ```ts
46
+ // BAD — same shape defined twice
47
+ // api/users.ts
48
+ interface ApiUser { id: string; name: string; }
49
+
50
+ // frontend/types.ts
51
+ interface FrontendUser { id: string; name: string; }
52
+
53
+ // GOOD — single schema, inferred types
54
+ const userSchema = z.object({ id: z.string(), name: z.string() });
55
+ type User = z.infer<typeof userSchema>;
56
+ ```
57
+
58
+ ## Type Gymnastics
59
+
60
+ ```ts
61
+ // BAD — deeply conditional mapped types that take 10 minutes to understand
62
+ type DeepPartialReadonly<T> =
63
+ T extends object
64
+ ? { readonly [K in keyof T]?: DeepPartialReadonly<T[K]> }
65
+ : T;
66
+
67
+ // If you need this, ask whether the data model is the real problem
68
+ ```
69
+
70
+ ## `@ts-ignore` Without Explanation
71
+
72
+ ```ts
73
+ // BAD
74
+ // @ts-ignore
75
+ doSomethingWeird(value);
76
+
77
+ // ACCEPTABLE — only when necessary, always with context
78
+ // @ts-expect-error — third-party type definition is wrong, see github.com/foo/bar/issues/123
79
+ doSomethingWeird(value);
80
+ ```
81
+
82
+ ## Non-null Assertions Without Verification
83
+
84
+ ```ts
85
+ // BAD — crashes at runtime if undefined
86
+ const name = user!.profile!.name;
87
+
88
+ // GOOD
89
+ const name = user?.profile?.name ?? "Anonymous";
90
+ ```
91
+
92
+ ## `object` or `{}` as a Type
93
+
94
+ ```ts
95
+ // BAD — accepts almost anything
96
+ function format(value: object): string { ... }
97
+ function format(value: {}): string { ... }
98
+
99
+ // GOOD — be explicit
100
+ function format(value: Record<string, string>): string { ... }
101
+ ```
102
+
103
+ ## See Also
104
+
105
+ - [`rules.md`](rules.md) — the positive rules these anti-patterns violate
106
+ - [`validation.md`](validation.md) — runtime validation for `unknown` data
107
+ - [`error-handling.md`](error-handling.md) — async, throwing, and preserving error context