create-velox-app 0.4.7 → 0.4.10

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 (43) hide show
  1. package/dist/index.js.map +1 -1
  2. package/dist/templates/compiler.d.ts.map +1 -1
  3. package/dist/templates/compiler.js.map +1 -1
  4. package/dist/templates/index.d.ts.map +1 -1
  5. package/dist/templates/index.js +9 -1
  6. package/dist/templates/index.js.map +1 -1
  7. package/dist/templates/placeholders.d.ts +5 -0
  8. package/dist/templates/placeholders.d.ts.map +1 -1
  9. package/dist/templates/placeholders.js +14 -3
  10. package/dist/templates/placeholders.js.map +1 -1
  11. package/dist/templates/shared/web-base.d.ts +1 -0
  12. package/dist/templates/shared/web-base.d.ts.map +1 -1
  13. package/dist/templates/shared/web-base.js +5 -0
  14. package/dist/templates/shared/web-base.js.map +1 -1
  15. package/dist/templates/trpc.d.ts +15 -0
  16. package/dist/templates/trpc.d.ts.map +1 -0
  17. package/dist/templates/trpc.js +89 -0
  18. package/dist/templates/trpc.js.map +1 -0
  19. package/dist/templates/types.d.ts +1 -1
  20. package/dist/templates/types.d.ts.map +1 -1
  21. package/dist/templates/types.js +6 -0
  22. package/dist/templates/types.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/templates/source/api/config/auth.ts +7 -0
  25. package/src/templates/source/api/index.auth.ts +9 -2
  26. package/src/templates/source/api/index.default.ts +2 -1
  27. package/src/templates/source/api/index.trpc.ts +64 -0
  28. package/src/templates/source/api/prisma.config.ts +1 -0
  29. package/src/templates/source/api/procedures/auth.ts +10 -4
  30. package/src/templates/source/api/procedures/health.ts +1 -1
  31. package/src/templates/source/api/procedures/users.auth.ts +24 -68
  32. package/src/templates/source/api/procedures/users.default.ts +28 -58
  33. package/src/templates/source/api/schemas/user.ts +9 -4
  34. package/src/templates/source/web/App.module.css +6 -2
  35. package/src/templates/source/web/api.ts +42 -0
  36. package/src/templates/source/web/main.tsx +43 -12
  37. package/src/templates/source/web/package.json +2 -1
  38. package/src/templates/source/web/routes/__root.tsx +23 -5
  39. package/src/templates/source/web/routes/about.tsx +8 -2
  40. package/src/templates/source/web/routes/index.auth.tsx +22 -24
  41. package/src/templates/source/web/routes/index.default.tsx +12 -18
  42. package/src/templates/source/web/routes/users.tsx +64 -49
  43. package/src/templates/source/web/vite.config.ts +4 -3
@@ -2,62 +2,25 @@
2
2
  * User Procedures
3
3
  *
4
4
  * CRUD procedures for user management with authentication guards.
5
+ * Clean implementation without boilerplate:
6
+ * - No manual DbUser/DbClient interfaces needed
7
+ * - No toUserResponse() transformation needed
8
+ * - Output schema automatically serializes Date → string via withTimestamps()
5
9
  */
6
10
 
7
11
  import {
8
12
  AuthError,
9
13
  authenticated,
10
- hasRole,
11
14
  defineProcedures,
12
15
  GuardError,
13
- procedure,
16
+ hasRole,
17
+ NotFoundError,
14
18
  paginationInputSchema,
19
+ procedure,
15
20
  z,
16
21
  } from '@veloxts/velox';
17
22
 
18
- import {
19
- CreateUserInput,
20
- UpdateUserInput,
21
- type User,
22
- UserSchema,
23
- } from '../schemas/user.js';
24
-
25
- // ============================================================================
26
- // Database Types
27
- // ============================================================================
28
-
29
- interface DbUser {
30
- id: string;
31
- name: string;
32
- email: string;
33
- createdAt: Date;
34
- updatedAt: Date;
35
- }
36
-
37
- interface DbClient {
38
- user: {
39
- findUnique: (args: { where: { id: string } }) => Promise<DbUser | null>;
40
- findMany: (args?: { skip?: number; take?: number }) => Promise<DbUser[]>;
41
- create: (args: { data: { name: string; email: string } }) => Promise<DbUser>;
42
- update: (args: { where: { id: string }; data: { name?: string; email?: string } }) => Promise<DbUser>;
43
- delete: (args: { where: { id: string } }) => Promise<DbUser>;
44
- count: () => Promise<number>;
45
- };
46
- }
47
-
48
- function getDb(ctx: { db: unknown }): DbClient {
49
- return ctx.db as DbClient;
50
- }
51
-
52
- function toUserResponse(dbUser: DbUser): User {
53
- return {
54
- id: dbUser.id,
55
- name: dbUser.name,
56
- email: dbUser.email,
57
- createdAt: dbUser.createdAt instanceof Date ? dbUser.createdAt.toISOString() : dbUser.createdAt,
58
- updatedAt: dbUser.updatedAt instanceof Date ? dbUser.updatedAt.toISOString() : dbUser.updatedAt,
59
- };
60
- }
23
+ import { CreateUserInput, UpdateUserInput, UserSchema } from '../schemas/user.js';
61
24
 
62
25
  // ============================================================================
63
26
  // User Procedures
@@ -66,11 +29,14 @@ function toUserResponse(dbUser: DbUser): User {
66
29
  export const userProcedures = defineProcedures('users', {
67
30
  getUser: procedure()
68
31
  .input(z.object({ id: z.string().uuid() }))
69
- .output(UserSchema.nullable())
32
+ .output(UserSchema)
70
33
  .query(async ({ input, ctx }) => {
71
- const db = getDb(ctx);
72
- const user = await db.user.findUnique({ where: { id: input.id } });
73
- return user ? toUserResponse(user) : null;
34
+ const user = await ctx.db.user.findUnique({ where: { id: input.id } });
35
+ if (!user) {
36
+ throw new NotFoundError(`User with id '${input.id}' not found`);
37
+ }
38
+ // Return Prisma object directly - output schema handles Date serialization
39
+ return user;
74
40
  }),
75
41
 
76
42
  listUsers: procedure()
@@ -86,20 +52,17 @@ export const userProcedures = defineProcedures('users', {
86
52
  })
87
53
  )
88
54
  .query(async ({ input, ctx }) => {
89
- const db = getDb(ctx);
90
55
  const page = input?.page ?? 1;
91
56
  const limit = input?.limit ?? 10;
92
57
  const skip = (page - 1) * limit;
93
58
 
94
- const [dbUsers, total] = await Promise.all([
95
- db.user.findMany({ skip, take: limit }),
96
- db.user.count(),
59
+ const [users, total] = await Promise.all([
60
+ ctx.db.user.findMany({ skip, take: limit }),
61
+ ctx.db.user.count(),
97
62
  ]);
98
63
 
99
- return {
100
- data: dbUsers.map(toUserResponse),
101
- meta: { page, limit, total },
102
- };
64
+ // Return Prisma objects directly - output schema serializes dates
65
+ return { data: users, meta: { page, limit, total } };
103
66
  }),
104
67
 
105
68
  createUser: procedure()
@@ -107,9 +70,7 @@ export const userProcedures = defineProcedures('users', {
107
70
  .input(CreateUserInput)
108
71
  .output(UserSchema)
109
72
  .mutation(async ({ input, ctx }) => {
110
- const db = getDb(ctx);
111
- const user = await db.user.create({ data: input });
112
- return toUserResponse(user);
73
+ return ctx.db.user.create({ data: input });
113
74
  }),
114
75
 
115
76
  updateUser: procedure()
@@ -117,7 +78,6 @@ export const userProcedures = defineProcedures('users', {
117
78
  .input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
118
79
  .output(UserSchema)
119
80
  .mutation(async ({ input, ctx }) => {
120
- const db = getDb(ctx);
121
81
  const { id, ...data } = input;
122
82
 
123
83
  if (!ctx.user) {
@@ -131,8 +91,7 @@ export const userProcedures = defineProcedures('users', {
131
91
  throw new GuardError('ownership', 'You can only update your own profile', 403);
132
92
  }
133
93
 
134
- const updated = await db.user.update({ where: { id }, data });
135
- return toUserResponse(updated);
94
+ return ctx.db.user.update({ where: { id }, data });
136
95
  }),
137
96
 
138
97
  patchUser: procedure()
@@ -140,7 +99,6 @@ export const userProcedures = defineProcedures('users', {
140
99
  .input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
141
100
  .output(UserSchema)
142
101
  .mutation(async ({ input, ctx }) => {
143
- const db = getDb(ctx);
144
102
  const { id, ...data } = input;
145
103
 
146
104
  if (!ctx.user) {
@@ -154,8 +112,7 @@ export const userProcedures = defineProcedures('users', {
154
112
  throw new GuardError('ownership', 'You can only update your own profile', 403);
155
113
  }
156
114
 
157
- const updated = await db.user.update({ where: { id }, data });
158
- return toUserResponse(updated);
115
+ return ctx.db.user.update({ where: { id }, data });
159
116
  }),
160
117
 
161
118
  deleteUser: procedure()
@@ -163,8 +120,7 @@ export const userProcedures = defineProcedures('users', {
163
120
  .input(z.object({ id: z.string().uuid() }))
164
121
  .output(z.object({ success: z.boolean() }))
165
122
  .mutation(async ({ input, ctx }) => {
166
- const db = getDb(ctx);
167
- await db.user.delete({ where: { id: input.id } });
123
+ await ctx.db.user.delete({ where: { id: input.id } });
168
124
  return { success: true };
169
125
  }),
170
126
  });
@@ -1,53 +1,33 @@
1
1
  /**
2
2
  * User Procedures
3
+ *
4
+ * Clean implementation without boilerplate:
5
+ * - No manual DbUser/DbClient interfaces needed
6
+ * - No toUserResponse() transformation needed
7
+ * - Output schema automatically serializes Date → string via withTimestamps()
3
8
  */
4
9
 
5
- import { defineProcedures, procedure, paginationInputSchema, z } from '@veloxts/velox';
10
+ import {
11
+ defineProcedures,
12
+ NotFoundError,
13
+ paginationInputSchema,
14
+ procedure,
15
+ z,
16
+ } from '@veloxts/velox';
6
17
 
7
18
  import { CreateUserInput, UpdateUserInput, UserSchema } from '../schemas/user.js';
8
19
 
9
- // Database types
10
- interface DbUser {
11
- id: string;
12
- name: string;
13
- email: string;
14
- createdAt: Date;
15
- updatedAt: Date;
16
- }
17
-
18
- interface DbClient {
19
- user: {
20
- findUnique: (args: { where: { id: string } }) => Promise<DbUser | null>;
21
- findMany: (args?: { skip?: number; take?: number }) => Promise<DbUser[]>;
22
- create: (args: { data: { name: string; email: string } }) => Promise<DbUser>;
23
- update: (args: { where: { id: string }; data: { name?: string; email?: string } }) => Promise<DbUser>;
24
- delete: (args: { where: { id: string } }) => Promise<DbUser>;
25
- count: () => Promise<number>;
26
- };
27
- }
28
-
29
- function getDb(ctx: { db: unknown }): DbClient {
30
- return ctx.db as DbClient;
31
- }
32
-
33
- function toUserResponse(dbUser: DbUser) {
34
- return {
35
- id: dbUser.id,
36
- name: dbUser.name,
37
- email: dbUser.email,
38
- createdAt: dbUser.createdAt.toISOString(),
39
- updatedAt: dbUser.updatedAt.toISOString(),
40
- };
41
- }
42
-
43
20
  export const userProcedures = defineProcedures('users', {
44
21
  getUser: procedure()
45
22
  .input(z.object({ id: z.string().uuid() }))
46
- .output(UserSchema.nullable())
23
+ .output(UserSchema)
47
24
  .query(async ({ input, ctx }) => {
48
- const db = getDb(ctx);
49
- const user = await db.user.findUnique({ where: { id: input.id } });
50
- return user ? toUserResponse(user) : null;
25
+ const user = await ctx.db.user.findUnique({ where: { id: input.id } });
26
+ if (!user) {
27
+ throw new NotFoundError(`User with id '${input.id}' not found`);
28
+ }
29
+ // Return Prisma object directly - output schema handles Date serialization
30
+ return user;
51
31
  }),
52
32
 
53
33
  listUsers: procedure()
@@ -63,57 +43,47 @@ export const userProcedures = defineProcedures('users', {
63
43
  })
64
44
  )
65
45
  .query(async ({ input, ctx }) => {
66
- const db = getDb(ctx);
67
46
  const page = input?.page ?? 1;
68
47
  const limit = input?.limit ?? 10;
69
48
  const skip = (page - 1) * limit;
70
49
 
71
- const [dbUsers, total] = await Promise.all([
72
- db.user.findMany({ skip, take: limit }),
73
- db.user.count(),
50
+ const [users, total] = await Promise.all([
51
+ ctx.db.user.findMany({ skip, take: limit }),
52
+ ctx.db.user.count(),
74
53
  ]);
75
54
 
76
- return {
77
- data: dbUsers.map(toUserResponse),
78
- meta: { page, limit, total },
79
- };
55
+ // Return Prisma objects directly - output schema serializes dates
56
+ return { data: users, meta: { page, limit, total } };
80
57
  }),
81
58
 
82
59
  createUser: procedure()
83
60
  .input(CreateUserInput)
84
61
  .output(UserSchema)
85
62
  .mutation(async ({ input, ctx }) => {
86
- const db = getDb(ctx);
87
- const user = await db.user.create({ data: input });
88
- return toUserResponse(user);
63
+ return ctx.db.user.create({ data: input });
89
64
  }),
90
65
 
91
66
  updateUser: procedure()
92
67
  .input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
93
68
  .output(UserSchema)
94
69
  .mutation(async ({ input, ctx }) => {
95
- const db = getDb(ctx);
96
70
  const { id, ...data } = input;
97
- const user = await db.user.update({ where: { id }, data });
98
- return toUserResponse(user);
71
+ return ctx.db.user.update({ where: { id }, data });
99
72
  }),
100
73
 
101
74
  patchUser: procedure()
102
75
  .input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
103
76
  .output(UserSchema)
104
77
  .mutation(async ({ input, ctx }) => {
105
- const db = getDb(ctx);
106
78
  const { id, ...data } = input;
107
- const user = await db.user.update({ where: { id }, data });
108
- return toUserResponse(user);
79
+ return ctx.db.user.update({ where: { id }, data });
109
80
  }),
110
81
 
111
82
  deleteUser: procedure()
112
83
  .input(z.object({ id: z.string().uuid() }))
113
84
  .output(z.object({ success: z.boolean() }))
114
85
  .mutation(async ({ input, ctx }) => {
115
- const db = getDb(ctx);
116
- await db.user.delete({ where: { id: input.id } });
86
+ await ctx.db.user.delete({ where: { id: input.id } });
117
87
  return { success: true };
118
88
  }),
119
89
  });
@@ -1,17 +1,22 @@
1
1
  /**
2
2
  * User Schemas
3
+ *
4
+ * Uses withTimestamps() for automatic Date → string serialization.
5
+ * No manual transformation needed in procedure handlers.
3
6
  */
4
7
 
5
- import { createIdSchema, emailSchema, z } from '@veloxts/velox';
8
+ import { createIdSchema, emailSchema, withTimestamps, z } from '@veloxts/velox';
6
9
 
7
- export const UserSchema = z.object({
10
+ // Business fields only - timestamps added separately
11
+ const UserFields = z.object({
8
12
  id: createIdSchema('uuid'),
9
13
  name: z.string().min(1).max(100),
10
14
  email: emailSchema,
11
- createdAt: z.coerce.date().transform((d) => d.toISOString()),
12
- updatedAt: z.coerce.date().transform((d) => d.toISOString()),
13
15
  });
14
16
 
17
+ // Complete schema with automatic Date → string serialization
18
+ export const UserSchema = withTimestamps(UserFields);
19
+
15
20
  export type User = z.infer<typeof UserSchema>;
16
21
 
17
22
  export const CreateUserInput = z.object({
@@ -99,7 +99,9 @@
99
99
  border: 1px solid #222;
100
100
  border-radius: 12px;
101
101
  padding: 1.5rem;
102
- transition: border-color 0.2s, transform 0.2s;
102
+ transition:
103
+ border-color 0.2s,
104
+ transform 0.2s;
103
105
  }
104
106
 
105
107
  .card:hover {
@@ -217,7 +219,9 @@
217
219
  font-size: 1rem;
218
220
  font-weight: 600;
219
221
  cursor: pointer;
220
- transition: opacity 0.2s, transform 0.1s;
222
+ transition:
223
+ opacity 0.2s,
224
+ transform 0.1s;
221
225
  }
222
226
 
223
227
  .button:hover {
@@ -0,0 +1,42 @@
1
+ /**
2
+ * VeloxTS API Hooks
3
+ *
4
+ * This file creates typed hooks for accessing your backend procedures.
5
+ * Import `api` in your components for full autocomplete support.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { api } from '@/api';
10
+ * import { useQueryClient } from '@veloxts/client/react';
11
+ *
12
+ * function UserProfile({ userId }: { userId: string }) {
13
+ * const queryClient = useQueryClient();
14
+ *
15
+ * const { data: user } = api.users.getUser.useQuery({ id: userId });
16
+ * const { mutate } = api.users.updateUser.useMutation({
17
+ * onSuccess: () => api.users.getUser.invalidate({ id: userId }, queryClient),
18
+ * });
19
+ * }
20
+ * ```
21
+ */
22
+
23
+ import { createVeloxHooks } from '@veloxts/client/react';
24
+
25
+ import type { AppRouter } from '../../api/src/index.js';
26
+
27
+ /**
28
+ * Type-safe API hooks with full autocomplete
29
+ *
30
+ * Hooks (call inside components):
31
+ * - api.namespace.procedure.useQuery(input, options)
32
+ * - api.namespace.procedure.useMutation(options)
33
+ * - api.namespace.procedure.useSuspenseQuery(input, options)
34
+ *
35
+ * Cache utilities (input first, queryClient last):
36
+ * - api.namespace.procedure.getQueryKey(input)
37
+ * - api.namespace.procedure.invalidate(input, queryClient)
38
+ * - api.namespace.procedure.prefetch(input, queryClient)
39
+ * - api.namespace.procedure.getData(input, queryClient)
40
+ * - api.namespace.procedure.setData(input, data, queryClient)
41
+ */
42
+ export const api = createVeloxHooks<AppRouter>();
@@ -1,12 +1,18 @@
1
+ import { createRouter, RouterProvider } from '@tanstack/react-router';
2
+ import { VeloxProvider } from '@veloxts/client/react';
1
3
  import { StrictMode } from 'react';
2
4
  import { createRoot } from 'react-dom/client';
3
- import { RouterProvider, createRouter } from '@tanstack/react-router';
4
- import { VeloxProvider } from '@veloxts/client/react';
5
+
5
6
  import { routeTree } from './routeTree.gen';
6
7
  import './styles/global.css';
7
8
 
8
9
  // Import router type from API for full type safety
9
10
  import type { AppRouter } from '../../api/src/index.js';
11
+ /* @if auth */
12
+ // Import routes directly from backend - no manual duplication needed
13
+ import { routes } from '../../api/src/index.js';
14
+
15
+ /* @endif auth */
10
16
 
11
17
  // Create router with route tree
12
18
  const router = createRouter({ routeTree });
@@ -25,15 +31,33 @@ const getAuthHeaders = () => {
25
31
  return token ? { Authorization: `Bearer ${token}` } : {};
26
32
  };
27
33
 
28
- // Route mappings for auth procedures with custom .rest() endpoints
29
- const routes = {
30
- auth: {
31
- createAccount: '/auth/register',
32
- createSession: '/auth/login',
33
- createRefresh: '/auth/refresh',
34
- deleteSession: '/auth/logout',
35
- getMe: '/auth/me',
36
- },
34
+ // Automatic token refresh on 401 responses
35
+ const handleUnauthorized = async (): Promise<boolean> => {
36
+ const refreshToken = localStorage.getItem('refreshToken');
37
+ if (!refreshToken) return false;
38
+
39
+ try {
40
+ const res = await fetch('/api/auth/refresh', {
41
+ method: 'POST',
42
+ headers: { 'Content-Type': 'application/json' },
43
+ body: JSON.stringify({ refreshToken }),
44
+ });
45
+
46
+ if (!res.ok) {
47
+ // Refresh failed - clear tokens
48
+ localStorage.removeItem('token');
49
+ localStorage.removeItem('refreshToken');
50
+ return false;
51
+ }
52
+
53
+ const data = (await res.json()) as { accessToken: string; refreshToken: string };
54
+ localStorage.setItem('token', data.accessToken);
55
+ localStorage.setItem('refreshToken', data.refreshToken);
56
+ return true; // Retry the original request
57
+ } catch {
58
+ // Network error during refresh
59
+ return false;
60
+ }
37
61
  };
38
62
  /* @endif auth */
39
63
 
@@ -49,7 +73,14 @@ createRoot(rootElement).render(
49
73
  </VeloxProvider>
50
74
  {/* @endif default */}
51
75
  {/* @if auth */}
52
- <VeloxProvider<AppRouter> config={{ baseUrl: '/api', headers: getAuthHeaders, routes }}>
76
+ <VeloxProvider<AppRouter>
77
+ config={{
78
+ baseUrl: '/api',
79
+ headers: getAuthHeaders,
80
+ routes,
81
+ onUnauthorized: handleUnauthorized,
82
+ }}
83
+ >
53
84
  <RouterProvider router={router} />
54
85
  </VeloxProvider>
55
86
  {/* @endif auth */}
@@ -14,7 +14,8 @@
14
14
  "@tanstack/react-router": "1.140.5",
15
15
  "@veloxts/client": "__VELOXTS_VERSION__",
16
16
  "react": "19.2.1",
17
- "react-dom": "19.2.1"
17
+ "react-dom": "19.2.1",
18
+ "react-error-boundary": "5.0.0"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@types/react": "19.2.7",
@@ -1,7 +1,8 @@
1
- import { createRootRoute, Outlet, Link } from '@tanstack/react-router';
1
+ import { createRootRoute, Link, Outlet } from '@tanstack/react-router';
2
2
  import styles from '@/App.module.css';
3
3
  /* @if auth */
4
4
  import { useQuery } from '@veloxts/client/react';
5
+
5
6
  import type { AppRouter } from '../../../api/src/index.js';
6
7
  /* @endif auth */
7
8
 
@@ -11,7 +12,12 @@ export const Route = createRootRoute({
11
12
 
12
13
  function RootLayout() {
13
14
  /* @if auth */
14
- const { data: user } = useQuery<AppRouter, 'auth', 'getMe'>('auth', 'getMe', {}, { retry: false });
15
+ const { data: user } = useQuery<AppRouter, 'auth', 'getMe'>(
16
+ 'auth',
17
+ 'getMe',
18
+ {},
19
+ { retry: false }
20
+ );
15
21
  const isAuthenticated = !!user;
16
22
  /* @endif auth */
17
23
 
@@ -28,18 +34,30 @@ function RootLayout() {
28
34
  Home
29
35
  </Link>
30
36
  {/* @if default */}
31
- <Link to="/users" className={styles.navLink} activeProps={{ className: styles.navLinkActive }}>
37
+ <Link
38
+ to="/users"
39
+ className={styles.navLink}
40
+ activeProps={{ className: styles.navLinkActive }}
41
+ >
32
42
  Users
33
43
  </Link>
34
44
  {/* @endif default */}
35
45
  {/* @if auth */}
36
46
  {isAuthenticated && (
37
- <Link to="/users" className={styles.navLink} activeProps={{ className: styles.navLinkActive }}>
47
+ <Link
48
+ to="/users"
49
+ className={styles.navLink}
50
+ activeProps={{ className: styles.navLinkActive }}
51
+ >
38
52
  Users
39
53
  </Link>
40
54
  )}
41
55
  {/* @endif auth */}
42
- <Link to="/about" className={styles.navLink} activeProps={{ className: styles.navLinkActive }}>
56
+ <Link
57
+ to="/about"
58
+ className={styles.navLink}
59
+ activeProps={{ className: styles.navLinkActive }}
60
+ >
43
61
  About
44
62
  </Link>
45
63
  </div>
@@ -1,4 +1,5 @@
1
1
  import { createFileRoute } from '@tanstack/react-router';
2
+
2
3
  import styles from '@/App.module.css';
3
4
 
4
5
  export const Route = createFileRoute('/about')({
@@ -18,12 +19,17 @@ function AboutPage() {
18
19
  <div className={styles.cards}>
19
20
  <div className={styles.card}>
20
21
  <h2>Type Safety</h2>
21
- <p>End-to-end type safety without code generation. Types flow from backend to frontend automatically.</p>
22
+ <p>
23
+ End-to-end type safety without code generation. Types flow from backend to frontend
24
+ automatically.
25
+ </p>
22
26
  </div>
23
27
 
24
28
  <div className={styles.card}>
25
29
  <h2>Developer Experience</h2>
26
- <p>Convention over configuration. Sensible defaults with escape hatches when you need them.</p>
30
+ <p>
31
+ Convention over configuration. Sensible defaults with escape hatches when you need them.
32
+ </p>
27
33
  </div>
28
34
 
29
35
  <div className={styles.card}>