create-edhor-stack 0.1.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/STACK.md +1086 -0
  4. package/dist/index.js +3181 -0
  5. package/package.json +44 -0
  6. package/templates/apps/api-elysia/package.json +21 -0
  7. package/templates/apps/api-elysia/src/index.ts +59 -0
  8. package/templates/apps/api-elysia/src/lib/eden.ts +25 -0
  9. package/templates/apps/api-elysia/src/lib/env.ts +18 -0
  10. package/templates/apps/api-elysia/src/routes/health.ts +13 -0
  11. package/templates/apps/api-elysia/src/routes/users.ts +117 -0
  12. package/templates/apps/api-elysia/tsconfig.json +15 -0
  13. package/templates/apps/api-hono/package.json +20 -0
  14. package/templates/apps/api-hono/src/index.ts +66 -0
  15. package/templates/apps/api-hono/src/lib/env.ts +18 -0
  16. package/templates/apps/api-hono/src/routes/health.ts +20 -0
  17. package/templates/apps/api-hono/src/routes/users.ts +110 -0
  18. package/templates/apps/api-hono/tsconfig.json +15 -0
  19. package/templates/apps/mobile/.env.example +9 -0
  20. package/templates/apps/mobile/app/_layout.tsx +16 -0
  21. package/templates/apps/mobile/app/index.tsx +39 -0
  22. package/templates/apps/mobile/app.json +37 -0
  23. package/templates/apps/mobile/assets/adaptive-icon.png +0 -0
  24. package/templates/apps/mobile/assets/favicon.png +0 -0
  25. package/templates/apps/mobile/assets/icon.png +0 -0
  26. package/templates/apps/mobile/assets/splash-icon.png +0 -0
  27. package/templates/apps/mobile/package.json +39 -0
  28. package/templates/apps/mobile/src/api/client.ts +51 -0
  29. package/templates/apps/mobile/src/api/index.ts +3 -0
  30. package/templates/apps/mobile/src/api/queries.ts +24 -0
  31. package/templates/apps/mobile/src/api/schemas.ts +32 -0
  32. package/templates/apps/mobile/src/lib/env.ts +40 -0
  33. package/templates/apps/mobile/src/lib/query-client.ts +28 -0
  34. package/templates/apps/mobile/src/lib/result.ts +45 -0
  35. package/templates/apps/mobile/src/lib/store.ts +63 -0
  36. package/templates/apps/mobile/tsconfig.json +10 -0
  37. package/templates/apps/web/.env.example +11 -0
  38. package/templates/apps/web/package.json +29 -0
  39. package/templates/apps/web/src/lib/env.ts +52 -0
  40. package/templates/apps/web/src/lib/queries.ts +27 -0
  41. package/templates/apps/web/src/lib/query-client.ts +11 -0
  42. package/templates/apps/web/src/router.tsx +17 -0
  43. package/templates/apps/web/src/routes/__root.tsx +32 -0
  44. package/templates/apps/web/src/routes/index.tsx +16 -0
  45. package/templates/apps/web/src/styles.css +26 -0
  46. package/templates/apps/web/tsconfig.json +10 -0
  47. package/templates/apps/web/vite.config.ts +21 -0
  48. package/templates/base/.claude/settings.json +33 -0
  49. package/templates/base/.claude/skills/add-api-endpoint.md +137 -0
  50. package/templates/base/.claude/skills/add-component.md +79 -0
  51. package/templates/base/.claude/skills/add-route.md +134 -0
  52. package/templates/base/.claude/skills/add-store.md +158 -0
  53. package/templates/base/.husky/pre-commit +1 -0
  54. package/templates/base/.lintstagedrc +4 -0
  55. package/templates/base/.node-version +1 -0
  56. package/templates/base/AGENTS.md +135 -0
  57. package/templates/base/CLAUDE.md.hbs +139 -0
  58. package/templates/base/Dockerfile +32 -0
  59. package/templates/base/biome.json +52 -0
  60. package/templates/base/fly.toml.hbs +20 -0
  61. package/templates/base/gitignore +36 -0
  62. package/templates/base/package.json.hbs +22 -0
  63. package/templates/base/tsconfig.json +14 -0
  64. package/templates/base/turbo.json +22 -0
  65. package/templates/packages/shared/package.json +17 -0
  66. package/templates/packages/shared/src/index.ts +4 -0
  67. package/templates/packages/shared/src/schemas.ts +50 -0
  68. package/templates/packages/shared/src/types.ts +47 -0
  69. package/templates/packages/shared/src/utils.ts +87 -0
  70. package/templates/packages/shared/tsconfig.json +14 -0
  71. package/templates/packages/stripe/package.json +18 -0
  72. package/templates/packages/stripe/src/client.ts +110 -0
  73. package/templates/packages/stripe/src/index.ts +3 -0
  74. package/templates/packages/stripe/src/schemas.ts +65 -0
  75. package/templates/packages/stripe/src/webhooks.ts +91 -0
  76. package/templates/packages/stripe/tsconfig.json +14 -0
  77. package/templates/packages/ui/components.json +19 -0
  78. package/templates/packages/ui/package.json +29 -0
  79. package/templates/packages/ui/src/components/button.tsx +58 -0
  80. package/templates/packages/ui/src/index.ts +5 -0
  81. package/templates/packages/ui/src/lib/utils.ts +6 -0
  82. package/templates/packages/ui/src/styles.css +120 -0
  83. package/templates/packages/ui/tsconfig.json +10 -0
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@{{name}}/mobile",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "main": "expo-router/entry",
6
+ "scripts": {
7
+ "dev": "expo start",
8
+ "android": "expo run:android",
9
+ "ios": "expo run:ios",
10
+ "build": "eas build"
11
+ },
12
+ "dependencies": {
13
+ "expo": "~54.0.0",
14
+ "expo-router": "~6.0.22",
15
+ "expo-status-bar": "~3.0.9",
16
+ "expo-linking": "~8.0.11",
17
+ "expo-constants": "~18.0.13",
18
+ "react": "19.1.0",
19
+ "react-native": "0.81.5",
20
+ "react-native-safe-area-context": "~5.6.0",
21
+ "react-native-screens": "~4.16.0",
22
+ "react-native-gesture-handler": "~2.28.0",
23
+ "react-native-reanimated": "~4.1.1",
24
+ "@tanstack/react-query": "^5.60.0",
25
+ "@tanstack/react-query-persist-client": "^5.60.0",
26
+ "@tanstack/query-async-storage-persister": "^5.60.0",
27
+ "@react-native-async-storage/async-storage": "^2.0.0",
28
+ "zustand": "^5.0.0",
29
+ "zod": "^3.24.0",
30
+ "@t3-oss/env-core": "^0.12.0",
31
+ "@shopify/flash-list": "2.0.2",
32
+ "lucide-react-native": "^0.460.0"
33
+ },
34
+ "devDependencies": {
35
+ "@babel/core": "^7.25.0",
36
+ "@types/react": "~19.1.10",
37
+ "typescript": "~5.9.2"
38
+ }
39
+ }
@@ -0,0 +1,51 @@
1
+ import type { z } from 'zod';
2
+
3
+ import { env } from '@/lib/env';
4
+
5
+ /**
6
+ * Fetch with Zod validation - validates API responses at runtime.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const user = await fetchValidated('/users/123', UserSchema);
11
+ * // user is typed as User and validated at runtime
12
+ * ```
13
+ */
14
+ export async function fetchValidated<T>(
15
+ path: string,
16
+ schema: z.ZodType<T>,
17
+ options?: RequestInit
18
+ ): Promise<T> {
19
+ const url = path.startsWith('http') ? path : `${env.EXPO_PUBLIC_API_URL}${path}`;
20
+
21
+ const response = await fetch(url, {
22
+ ...options,
23
+ headers: {
24
+ 'Content-Type': 'application/json',
25
+ ...options?.headers,
26
+ },
27
+ });
28
+
29
+ if (!response.ok) {
30
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
31
+ }
32
+
33
+ const data = await response.json();
34
+ return schema.parse(data);
35
+ }
36
+
37
+ /**
38
+ * POST with Zod validation
39
+ */
40
+ export async function postValidated<T>(
41
+ path: string,
42
+ body: unknown,
43
+ schema: z.ZodType<T>,
44
+ options?: RequestInit
45
+ ): Promise<T> {
46
+ return fetchValidated(path, schema, {
47
+ ...options,
48
+ method: 'POST',
49
+ body: JSON.stringify(body),
50
+ });
51
+ }
@@ -0,0 +1,3 @@
1
+ export * from './client';
2
+ export * from './queries';
3
+ export * from './schemas';
@@ -0,0 +1,24 @@
1
+ import { queryOptions } from '@tanstack/react-query';
2
+
3
+ import { fetchValidated } from './client';
4
+ import { ItemSchema, PaginatedResponseSchema, UserSchema } from './schemas';
5
+
6
+ // ============================================================================
7
+ // QUERY OPTIONS - Replace with your actual queries
8
+ // ============================================================================
9
+
10
+ export const userQueryOptions = (userId: string) =>
11
+ queryOptions({
12
+ queryKey: ['user', userId],
13
+ queryFn: () => fetchValidated(`/users/${userId}`, UserSchema),
14
+ });
15
+
16
+ export const itemsQueryOptions = (cursor?: string) =>
17
+ queryOptions({
18
+ queryKey: ['items', { cursor }],
19
+ queryFn: () =>
20
+ fetchValidated(
21
+ `/items${cursor ? `?cursor=${cursor}` : ''}`,
22
+ PaginatedResponseSchema(ItemSchema)
23
+ ),
24
+ });
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+
3
+ // ============================================================================
4
+ // EXAMPLE SCHEMAS - Replace with your actual API schemas
5
+ // ============================================================================
6
+
7
+ export const UserSchema = z.object({
8
+ id: z.string(),
9
+ email: z.string().email(),
10
+ name: z.string(),
11
+ avatar: z.string().url().optional(),
12
+ createdAt: z.string().datetime(),
13
+ });
14
+
15
+ export type User = z.infer<typeof UserSchema>;
16
+
17
+ export const ItemSchema = z.object({
18
+ id: z.string(),
19
+ title: z.string(),
20
+ description: z.string().optional(),
21
+ createdAt: z.string().datetime(),
22
+ updatedAt: z.string().datetime(),
23
+ });
24
+
25
+ export type Item = z.infer<typeof ItemSchema>;
26
+
27
+ export const PaginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
28
+ z.object({
29
+ items: z.array(itemSchema),
30
+ nextCursor: z.string().nullable(),
31
+ hasMore: z.boolean(),
32
+ });
@@ -0,0 +1,40 @@
1
+ import { createEnv } from '@t3-oss/env-core';
2
+ import { z } from 'zod';
3
+
4
+ export const env = createEnv({
5
+ /**
6
+ * Server-side environment variables.
7
+ * These should only be used in API routes or server functions.
8
+ */
9
+ server: {
10
+ // API_SECRET: z.string().min(32),
11
+ },
12
+
13
+ /**
14
+ * Client-side environment variables (available everywhere).
15
+ * Must be prefixed with EXPO_PUBLIC_ in .env files.
16
+ */
17
+ clientPrefix: 'EXPO_PUBLIC_',
18
+ client: {
19
+ EXPO_PUBLIC_API_URL: z.string().url().default('https://api.example.com'),
20
+ // EXPO_PUBLIC_SENTRY_DSN: z.string().url().optional(),
21
+ },
22
+
23
+ /**
24
+ * Runtime environment - manually specify all variables.
25
+ */
26
+ runtimeEnv: {
27
+ EXPO_PUBLIC_API_URL: process.env.EXPO_PUBLIC_API_URL,
28
+ // Add all variables here
29
+ },
30
+
31
+ /**
32
+ * Treat empty strings as undefined.
33
+ */
34
+ emptyStringAsUndefined: true,
35
+
36
+ /**
37
+ * Skip validation during builds if needed.
38
+ */
39
+ skipValidation: process.env.SKIP_ENV_VALIDATION === 'true',
40
+ });
@@ -0,0 +1,28 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+ import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
3
+ import { QueryClient } from '@tanstack/react-query';
4
+
5
+ export const queryClient = new QueryClient({
6
+ defaultOptions: {
7
+ queries: {
8
+ // Keep cached data for 7 days (offline support)
9
+ gcTime: 1000 * 60 * 60 * 24 * 7,
10
+ // Data considered fresh for 5 minutes
11
+ staleTime: 1000 * 60 * 5,
12
+ // Try cache first, then network
13
+ networkMode: 'offlineFirst',
14
+ // Retry with exponential backoff
15
+ retry: 3,
16
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
17
+ },
18
+ mutations: {
19
+ networkMode: 'offlineFirst',
20
+ },
21
+ },
22
+ });
23
+
24
+ // Persist to AsyncStorage for true offline support
25
+ export const persister = createAsyncStoragePersister({
26
+ storage: AsyncStorage,
27
+ key: 'REACT_QUERY_OFFLINE_CACHE',
28
+ });
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Result type for explicit error handling without try-catch everywhere.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * async function fetchUser(id: string): Promise<Result<User, string>> {
7
+ * try {
8
+ * const user = await api.getUser(id);
9
+ * return ok(user);
10
+ * } catch (e) {
11
+ * return err(e instanceof Error ? e.message : 'Unknown error');
12
+ * }
13
+ * }
14
+ *
15
+ * // Usage
16
+ * const result = await fetchUser('123');
17
+ * if (!result.ok) {
18
+ * showToast(result.error);
19
+ * return;
20
+ * }
21
+ * const user = result.value;
22
+ * ```
23
+ */
24
+ export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
25
+
26
+ export const ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
27
+ export const err = <E>(error: E): Result<never, E> => ({ ok: false, error });
28
+
29
+ /**
30
+ * Unwrap a Result, throwing if it's an error.
31
+ * Only use this when you're certain the result is ok.
32
+ */
33
+ export function unwrap<T, E>(result: Result<T, E>): T {
34
+ if (!result.ok) {
35
+ throw result.error;
36
+ }
37
+ return result.value;
38
+ }
39
+
40
+ /**
41
+ * Unwrap a Result with a default value for errors.
42
+ */
43
+ export function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
44
+ return result.ok ? result.value : defaultValue;
45
+ }
@@ -0,0 +1,63 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+ import { create } from 'zustand';
3
+ import { createJSONStorage, persist } from 'zustand/middleware';
4
+
5
+ // ============================================================================
6
+ // APP STORE
7
+ // ============================================================================
8
+
9
+ interface AppState {
10
+ fontSize: 'small' | 'medium' | 'large';
11
+ theme: 'light' | 'dark' | 'system';
12
+ setFontSize: (size: AppState['fontSize']) => void;
13
+ setTheme: (theme: AppState['theme']) => void;
14
+ }
15
+
16
+ export const useAppStore = create<AppState>()(
17
+ persist(
18
+ (set) => ({
19
+ fontSize: 'medium',
20
+ theme: 'system',
21
+ setFontSize: (fontSize) => set({ fontSize }),
22
+ setTheme: (theme) => set({ theme }),
23
+ }),
24
+ {
25
+ name: 'app-storage',
26
+ storage: createJSONStorage(() => AsyncStorage),
27
+ }
28
+ )
29
+ );
30
+
31
+ // ============================================================================
32
+ // SEARCH STORE
33
+ // ============================================================================
34
+
35
+ interface SearchState {
36
+ query: string;
37
+ recentSearches: string[];
38
+ setQuery: (query: string) => void;
39
+ addRecentSearch: (search: string) => void;
40
+ clearRecentSearches: () => void;
41
+ }
42
+
43
+ export const useSearchStore = create<SearchState>()(
44
+ persist(
45
+ (set) => ({
46
+ query: '',
47
+ recentSearches: [],
48
+ setQuery: (query) => set({ query }),
49
+ addRecentSearch: (search) =>
50
+ set((state) => ({
51
+ recentSearches: [search, ...state.recentSearches.filter((s) => s !== search)].slice(
52
+ 0,
53
+ 10
54
+ ),
55
+ })),
56
+ clearRecentSearches: () => set({ recentSearches: [] }),
57
+ }),
58
+ {
59
+ name: 'search-storage',
60
+ storage: createJSONStorage(() => AsyncStorage),
61
+ }
62
+ )
63
+ );
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "expo/tsconfig.base",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "paths": {
6
+ "@/*": ["./src/*"]
7
+ }
8
+ },
9
+ "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
10
+ }
@@ -0,0 +1,11 @@
1
+ # Environment Variables
2
+ # Copy this file to .env.local and fill in the values
3
+
4
+ # Server-side only (not exposed to client)
5
+ NODE_ENV=development
6
+ # DATABASE_URL=postgresql://user:password@localhost:5432/db
7
+ # API_SECRET=your-secret-key-at-least-32-characters
8
+
9
+ # Client-side (prefixed with VITE_)
10
+ VITE_APP_URL=http://localhost:3000
11
+ # VITE_PUBLIC_API_URL=https://api.example.com
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@{{name}}/web",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "start": "node .output/server/index.mjs"
9
+ },
10
+ "dependencies": {
11
+ "@tanstack/react-router": "^1.154.14",
12
+ "@tanstack/react-start": "^1.154.14",
13
+ "@tanstack/react-query": "^5.60.0",
14
+ "@t3-oss/env-core": "^0.12.0",
15
+ "react": "^19.0.0",
16
+ "react-dom": "^19.0.0",
17
+ "zod": "^3.24.0",
18
+ "lucide-react": "^0.460.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/react": "^19.0.0",
22
+ "@types/react-dom": "^19.0.0",
23
+ "@vitejs/plugin-react": "^4.4.1",
24
+ "vite": "^6.3.0",
25
+ "vite-tsconfig-paths": "^5.1.4",
26
+ "tailwindcss": "^4.0.0",
27
+ "@tailwindcss/vite": "^4.0.0"
28
+ }
29
+ }
@@ -0,0 +1,52 @@
1
+ import { createEnv } from '@t3-oss/env-core';
2
+ import { z } from 'zod';
3
+
4
+ export const env = createEnv({
5
+ /**
6
+ * Server-side environment variables (not available on client).
7
+ * Will throw if accessed on the client.
8
+ */
9
+ server: {
10
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
11
+ // DATABASE_URL: z.string().url(),
12
+ // API_SECRET: z.string().min(32),
13
+ },
14
+
15
+ /**
16
+ * Client-side environment variables (available everywhere).
17
+ * Must be prefixed with VITE_ in .env files.
18
+ */
19
+ clientPrefix: 'VITE_',
20
+ client: {
21
+ VITE_APP_URL: z.string().url().default('http://localhost:3000'),
22
+ // VITE_PUBLIC_API_URL: z.string().url(),
23
+ },
24
+
25
+ /**
26
+ * Shared variables (available on both client and server).
27
+ */
28
+ shared: {
29
+ // NODE_ENV is commonly shared
30
+ },
31
+
32
+ /**
33
+ * Runtime environment - manually specify all variables.
34
+ * Required for bundlers that don't automatically inline process.env.
35
+ */
36
+ runtimeEnv: {
37
+ NODE_ENV: process.env.NODE_ENV,
38
+ VITE_APP_URL: process.env.VITE_APP_URL,
39
+ // Add all variables here
40
+ },
41
+
42
+ /**
43
+ * Treat empty strings as undefined.
44
+ * Useful for optional variables.
45
+ */
46
+ emptyStringAsUndefined: true,
47
+
48
+ /**
49
+ * Skip validation in certain environments.
50
+ */
51
+ skipValidation: process.env.SKIP_ENV_VALIDATION === 'true',
52
+ });
@@ -0,0 +1,27 @@
1
+ import { queryOptions } from '@tanstack/react-query';
2
+
3
+ // ============================================================================
4
+ // EXAMPLE QUERY OPTIONS - Replace with your actual queries
5
+ // ============================================================================
6
+
7
+ export const exampleQueryOptions = queryOptions({
8
+ queryKey: ['example'],
9
+ queryFn: async () => {
10
+ // Replace with your actual API call
11
+ return { message: 'Hello from TanStack Query!' };
12
+ },
13
+ staleTime: 1000 * 60 * 5, // 5 minutes
14
+ });
15
+
16
+ /**
17
+ * Query options factory pattern for dynamic queries
18
+ */
19
+ export const itemQueryOptions = (id: string) =>
20
+ queryOptions({
21
+ queryKey: ['items', id],
22
+ queryFn: async () => {
23
+ const response = await fetch(`/api/items/${id}`);
24
+ if (!response.ok) throw new Error('Failed to fetch item');
25
+ return response.json();
26
+ },
27
+ });
@@ -0,0 +1,11 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+
3
+ export const queryClient = new QueryClient({
4
+ defaultOptions: {
5
+ queries: {
6
+ staleTime: 1000 * 60 * 5, // 5 minutes
7
+ retry: 3,
8
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
9
+ },
10
+ },
11
+ });
@@ -0,0 +1,17 @@
1
+ import { createRouter } from '@tanstack/react-router';
2
+ import { routeTree } from './routeTree.gen';
3
+
4
+ export function getRouter() {
5
+ const router = createRouter({
6
+ routeTree,
7
+ defaultPreload: 'intent',
8
+ scrollRestoration: true,
9
+ });
10
+ return router;
11
+ }
12
+
13
+ declare module '@tanstack/react-router' {
14
+ interface Register {
15
+ router: ReturnType<typeof getRouter>;
16
+ }
17
+ }
@@ -0,0 +1,32 @@
1
+ import { QueryClientProvider } from '@tanstack/react-query';
2
+ import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
3
+
4
+ import { queryClient } from '../lib/query-client';
5
+ import '../styles.css';
6
+
7
+ import type { QueryClient } from '@tanstack/react-query';
8
+
9
+ export interface RouterContext {
10
+ queryClient: QueryClient;
11
+ }
12
+
13
+ export const Route = createRootRouteWithContext<RouterContext>()({
14
+ component: RootLayout,
15
+ });
16
+
17
+ function RootLayout() {
18
+ return (
19
+ <QueryClientProvider client={queryClient}>
20
+ <html lang="en">
21
+ <head>
22
+ <meta charSet="UTF-8" />
23
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
24
+ <title>{{name}}</title>
25
+ </head>
26
+ <body className="min-h-screen bg-background text-foreground antialiased">
27
+ <Outlet />
28
+ </body>
29
+ </html>
30
+ </QueryClientProvider>
31
+ );
32
+ }
@@ -0,0 +1,16 @@
1
+ import { createFileRoute } from '@tanstack/react-router';
2
+
3
+ export const Route = createFileRoute('/')({
4
+ component: Home,
5
+ });
6
+
7
+ function Home() {
8
+ return (
9
+ <main className="flex min-h-screen flex-col items-center justify-center p-8">
10
+ <h1 className="text-4xl font-bold tracking-tight">Welcome to {{name}}</h1>
11
+ <p className="mt-4 text-muted-foreground">
12
+ Edit <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-sm">src/routes/index.tsx</code> to get started.
13
+ </p>
14
+ </main>
15
+ );
16
+ }
@@ -0,0 +1,26 @@
1
+ @import 'tailwindcss';
2
+
3
+ @theme {
4
+ --color-background: oklch(1 0 0);
5
+ --color-foreground: oklch(0.1 0 0);
6
+ --color-primary: oklch(0.5 0.2 250);
7
+ --color-primary-foreground: oklch(1 0 0);
8
+ --color-muted: oklch(0.95 0 0);
9
+ --color-muted-foreground: oklch(0.4 0 0);
10
+ --color-border: oklch(0.9 0 0);
11
+ --font-sans: 'Inter', system-ui, sans-serif;
12
+ }
13
+
14
+ @media (prefers-color-scheme: dark) {
15
+ @theme {
16
+ --color-background: oklch(0.1 0 0);
17
+ --color-foreground: oklch(0.95 0 0);
18
+ --color-muted: oklch(0.2 0 0);
19
+ --color-muted-foreground: oklch(0.6 0 0);
20
+ --color-border: oklch(0.25 0 0);
21
+ }
22
+ }
23
+
24
+ body {
25
+ font-family: var(--font-sans);
26
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "paths": {
6
+ "@/*": ["./src/*"]
7
+ }
8
+ },
9
+ "include": ["src"]
10
+ }
@@ -0,0 +1,21 @@
1
+ import { tanstackStart } from '@tanstack/react-start/plugin/vite';
2
+ import tailwindcss from '@tailwindcss/vite';
3
+ import viteReact from '@vitejs/plugin-react';
4
+ import { defineConfig } from 'vite';
5
+ import tsConfigPaths from 'vite-tsconfig-paths';
6
+
7
+ export default defineConfig({
8
+ server: {
9
+ port: 3000,
10
+ },
11
+ plugins: [
12
+ tailwindcss(),
13
+ tsConfigPaths({
14
+ projects: ['./tsconfig.json'],
15
+ }),
16
+ tanstackStart({
17
+ srcDirectory: 'src',
18
+ }),
19
+ viteReact(),
20
+ ],
21
+ });
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "https://claude.ai/schemas/claude-code-settings.json",
3
+ "permissions": {
4
+ "allow": [
5
+ "Bash(bun *)",
6
+ "Bash(turbo *)",
7
+ "Bash(git status)",
8
+ "Bash(git diff*)",
9
+ "Bash(git log*)",
10
+ "Bash(git add*)",
11
+ "Bash(git commit*)",
12
+ "Bash(git branch*)",
13
+ "Bash(git checkout*)",
14
+ "Bash(git switch*)",
15
+ "Bash(npx expo*)",
16
+ "Bash(eas *)"
17
+ ],
18
+ "deny": [
19
+ "Bash(rm -rf /)",
20
+ "Bash(git push --force)",
21
+ "Bash(git reset --hard)"
22
+ ]
23
+ },
24
+ "instructions": [
25
+ "Always use Biome for linting: `bun check` not eslint",
26
+ "Use @/ import alias for src/ paths",
27
+ "Follow existing patterns in the codebase",
28
+ "Write tests before implementation (TDD)",
29
+ "Use Zustand selectors, never destructure entire store",
30
+ "Validate all API responses with Zod schemas",
31
+ "Keep commits small and focused"
32
+ ]
33
+ }