create-aron-app 0.1.7 → 0.1.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 (169) hide show
  1. package/.cursor/worktrees.json +3 -0
  2. package/README.md +24 -31
  3. package/dist/index.js +38 -49
  4. package/package.json +3 -7
  5. package/templates/.cursor/rules/backend.mdc +112 -0
  6. package/templates/.cursor/rules/coding_standards.mdc +85 -4
  7. package/templates/.cursor/rules/frontend_architecture.mdc +334 -0
  8. package/templates/.env.example +6 -0
  9. package/templates/apps/{react-router → web}/.react-router/types/+routes.ts +11 -6
  10. package/templates/apps/{react-router/.react-router/types/src/routes/(dashboard)/todos → web/.react-router/types/src/routes/(dashboard)/(todos)}/+types/[id].ts +5 -2
  11. package/templates/apps/{react-router/.react-router/types/src/routes/(dashboard)/todos → web/.react-router/types/src/routes/(dashboard)/(todos)}/+types/index.ts +5 -2
  12. package/templates/apps/web/.react-router/types/src/routes/(dashboard)/(todos)/+types/layout.ts +65 -0
  13. package/templates/apps/{react-router → web}/project.json +11 -4
  14. package/templates/apps/{react-router → web}/react-router.config.ts +1 -1
  15. package/templates/apps/web/src/libs/convex_query_client.ts +11 -0
  16. package/templates/apps/web/src/libs/react_query_client.ts +17 -0
  17. package/templates/apps/web/src/libs/server/auth.ts +32 -0
  18. package/templates/apps/web/src/libs/server/protected.ts +17 -0
  19. package/templates/apps/web/src/providers/api_auth_provider.tsx +26 -0
  20. package/templates/apps/web/src/providers/global_provider.tsx +28 -0
  21. package/templates/apps/web/src/providers/navigation_loading_bar_provider.tsx +72 -0
  22. package/templates/apps/web/src/root.tsx +68 -0
  23. package/templates/apps/web/src/routes/(dashboard)/(todos)/[id].tsx +33 -0
  24. package/templates/apps/web/src/routes/(dashboard)/(todos)/index.tsx +26 -0
  25. package/templates/apps/web/src/routes/(dashboard)/(todos)/layout.tsx +9 -0
  26. package/templates/apps/{react-router → web}/src/routes/(dashboard)/index.tsx +3 -2
  27. package/templates/apps/web/src/routes/(dashboard)/layout.tsx +20 -0
  28. package/templates/apps/{react-router → web}/src/routes.ts +4 -2
  29. package/templates/apps/{react-router → web}/src/surfaces/sidebar/install.tsx +1 -5
  30. package/templates/apps/{react-router → web}/src/surfaces/sidebar/layout.tsx +24 -15
  31. package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/all_todos.tsx +1 -1
  32. package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/all_todos_controller.ts +1 -1
  33. package/templates/apps/web/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
  34. package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/bootstrap.ts +4 -5
  35. package/templates/apps/{nextjs → web}/src/surfaces/todos/single_todo/header/create.tsx +4 -6
  36. package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/single_todo_controller.ts +1 -1
  37. package/templates/components.json +20 -0
  38. package/templates/gitignore +60 -0
  39. package/templates/nx.json +0 -11
  40. package/templates/package.json +2 -3
  41. package/templates/shared/assets/src/styles/global.css +14 -8
  42. package/templates/shared/ui/src/base/collapsible.tsx +31 -0
  43. package/templates/shared/ui/src/base/hover-card.tsx +42 -0
  44. package/templates/shared/ui/src/base/input-group.tsx +168 -0
  45. package/templates/shared/ui/src/base/panel.tsx +93 -0
  46. package/templates/shared/ui/src/hooks/use_mobile.tsx +1 -1
  47. package/templates/shared/ui/src/hooks/use_query_params.tsx +6 -7
  48. package/templates/shared/utils/src/convex.ts +2 -1
  49. package/templates/tsconfig.base.json +2 -4
  50. package/templates/.cursor/commands/builder.md +0 -0
  51. package/templates/.cursor/rules/api_architecture.mdc +0 -262
  52. package/templates/.cursor/rules/convex_rules.mdc +0 -331
  53. package/templates/.cursor/rules/frontend_architecture_core.mdc +0 -495
  54. package/templates/.cursor/rules/frontend_architecture_nextjs.mdc +0 -458
  55. package/templates/.cursor/rules/frontend_architecture_reactrouter.mdc +0 -473
  56. package/templates/.github/workflows/ci.yml +0 -29
  57. package/templates/apps/api/tsconfig.json +0 -23
  58. package/templates/apps/nextjs/.env.example +0 -10
  59. package/templates/apps/nextjs/index.d.ts +0 -6
  60. package/templates/apps/nextjs/next-env.d.ts +0 -5
  61. package/templates/apps/nextjs/next.config.js +0 -22
  62. package/templates/apps/nextjs/postcss.config.js +0 -17
  63. package/templates/apps/nextjs/project.json +0 -22
  64. package/templates/apps/nextjs/src/app/(auth)/layout.tsx +0 -21
  65. package/templates/apps/nextjs/src/app/(auth)/not-allowed/page.tsx +0 -23
  66. package/templates/apps/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -15
  67. package/templates/apps/nextjs/src/app/(dashboard)/layout.tsx +0 -22
  68. package/templates/apps/nextjs/src/app/(dashboard)/page.tsx +0 -12
  69. package/templates/apps/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +0 -26
  70. package/templates/apps/nextjs/src/app/(dashboard)/todos/page.tsx +0 -19
  71. package/templates/apps/nextjs/src/app/app.css +0 -3
  72. package/templates/apps/nextjs/src/app/layout.tsx +0 -26
  73. package/templates/apps/nextjs/src/middleware.ts +0 -18
  74. package/templates/apps/nextjs/src/providers/convex_provider.tsx +0 -44
  75. package/templates/apps/nextjs/src/surfaces/home/home.tsx +0 -27
  76. package/templates/apps/nextjs/src/surfaces/home/layout.tsx +0 -44
  77. package/templates/apps/nextjs/src/surfaces/home/main/create.tsx +0 -34
  78. package/templates/apps/nextjs/src/surfaces/sidebar/install.tsx +0 -23
  79. package/templates/apps/nextjs/src/surfaces/sidebar/layout.tsx +0 -118
  80. package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/create.tsx +0 -19
  81. package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_config.ts +0 -22
  82. package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_main.tsx +0 -25
  83. package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/create.tsx +0 -21
  84. package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +0 -33
  85. package/templates/apps/nextjs/src/surfaces/sidebar/sidebar.tsx +0 -23
  86. package/templates/apps/nextjs/src/surfaces/sidebar/ui/sidebar_nav_link.tsx +0 -39
  87. package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/create.tsx +0 -28
  88. package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/user_menu.tsx +0 -42
  89. package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos.tsx +0 -29
  90. package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos_controller.ts +0 -61
  91. package/templates/apps/nextjs/src/surfaces/todos/all_todos/bootstrap.ts +0 -21
  92. package/templates/apps/nextjs/src/surfaces/todos/all_todos/header/create.tsx +0 -23
  93. package/templates/apps/nextjs/src/surfaces/todos/all_todos/install.tsx +0 -23
  94. package/templates/apps/nextjs/src/surfaces/todos/all_todos/layout.tsx +0 -44
  95. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/create.tsx +0 -49
  96. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/main.tsx +0 -70
  97. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +0 -56
  98. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +0 -99
  99. package/templates/apps/nextjs/src/surfaces/todos/single_todo/bootstrap.ts +0 -32
  100. package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/header.tsx +0 -22
  101. package/templates/apps/nextjs/src/surfaces/todos/single_todo/install.tsx +0 -27
  102. package/templates/apps/nextjs/src/surfaces/todos/single_todo/layout.tsx +0 -55
  103. package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/create.tsx +0 -38
  104. package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/main.tsx +0 -49
  105. package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo.tsx +0 -29
  106. package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo_controller.ts +0 -13
  107. package/templates/apps/nextjs/src/utils/auth.ts +0 -18
  108. package/templates/apps/nextjs/src/utils/convex.ts +0 -11
  109. package/templates/apps/nextjs/src/utils/font.ts +0 -9
  110. package/templates/apps/nextjs/tsconfig.json +0 -42
  111. package/templates/apps/react-router/src/providers/api_auth_provider.tsx +0 -40
  112. package/templates/apps/react-router/src/root.tsx +0 -37
  113. package/templates/apps/react-router/src/routes/(dashboard)/layout.tsx +0 -37
  114. package/templates/apps/react-router/src/routes/(dashboard)/todos/[id].tsx +0 -19
  115. package/templates/apps/react-router/src/routes/(dashboard)/todos/index.tsx +0 -19
  116. package/templates/apps/react-router/src/surfaces/home/bootstrap.ts +0 -9
  117. package/templates/apps/react-router/src/surfaces/home/install.tsx +0 -17
  118. package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/create.tsx +0 -21
  119. package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +0 -31
  120. package/templates/apps/react-router/src/surfaces/todos/all_todos/bootstrap.ts +0 -18
  121. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +0 -11
  122. package/templates/apps/react-router/src/surfaces/todos/single_todo/header/create.tsx +0 -32
  123. package/templates/apps/react-router/tsconfig.json +0 -20
  124. package/templates/biome.json +0 -121
  125. package/templates/bun.lock +0 -3187
  126. package/templates/emails/tsconfig.json +0 -5
  127. package/templates/shared/assets/tsconfig.json +0 -5
  128. package/templates/shared/ui/tsconfig.json +0 -8
  129. package/templates/shared/utils/tsconfig.json +0 -5
  130. /package/templates/apps/{react-router → web}/.env.example +0 -0
  131. /package/templates/apps/{react-router → web}/.react-router/types/+future.ts +0 -0
  132. /package/templates/apps/{react-router → web}/.react-router/types/+server-build.d.ts +0 -0
  133. /package/templates/apps/{react-router → web}/.react-router/types/src/+types/root.ts +0 -0
  134. /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(auth)/+types/layout.ts +0 -0
  135. /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +0 -0
  136. /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(dashboard)/+types/index.ts +0 -0
  137. /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(dashboard)/+types/layout.ts +0 -0
  138. /package/templates/apps/{react-router → web}/postcss.config.js +0 -0
  139. /package/templates/apps/{react-router → web}/public/favicon.ico +0 -0
  140. /package/templates/apps/{react-router → web}/src/app.css +0 -0
  141. /package/templates/apps/{react-router → web}/src/components/error_boundary.tsx +0 -0
  142. /package/templates/apps/{react-router → web}/src/routes/(auth)/layout.tsx +0 -0
  143. /package/templates/apps/{react-router → web}/src/routes/(auth)/sign-in/index.tsx +0 -0
  144. /package/templates/apps/{nextjs → web}/src/surfaces/home/bootstrap.ts +0 -0
  145. /package/templates/apps/{react-router → web}/src/surfaces/home/home.tsx +0 -0
  146. /package/templates/apps/{nextjs → web}/src/surfaces/home/install.tsx +0 -0
  147. /package/templates/apps/{react-router → web}/src/surfaces/home/layout.tsx +0 -0
  148. /package/templates/apps/{react-router → web}/src/surfaces/home/main/create.tsx +0 -0
  149. /package/templates/apps/{react-router → web}/src/surfaces/sidebar/nav_main/create.tsx +0 -0
  150. /package/templates/apps/{react-router → web}/src/surfaces/sidebar/nav_main/nav_main.tsx +0 -0
  151. /package/templates/apps/{react-router → web}/src/surfaces/sidebar/sidebar.tsx +0 -0
  152. /package/templates/apps/{react-router → web}/src/surfaces/sidebar/user_menu/create.tsx +0 -0
  153. /package/templates/apps/{react-router → web}/src/surfaces/sidebar/user_menu/user_menu.tsx +0 -0
  154. /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/header/create.tsx +0 -0
  155. /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/install.tsx +0 -0
  156. /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/layout.tsx +0 -0
  157. /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/create.tsx +0 -0
  158. /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/main.tsx +0 -0
  159. /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +0 -0
  160. /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +0 -0
  161. /package/templates/apps/{nextjs → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +0 -0
  162. /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/header/header.tsx +0 -0
  163. /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/install.tsx +0 -0
  164. /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/layout.tsx +0 -0
  165. /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/main/create.tsx +0 -0
  166. /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/main/main.tsx +0 -0
  167. /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/single_todo.tsx +0 -0
  168. /package/templates/apps/{react-router → web}/vite.config.ts +0 -0
  169. /package/templates/emails/{welcome_email.tsx → src/welcome_email.tsx} +0 -0
@@ -1,473 +0,0 @@
1
- ---
2
- description: Frontend surface architecture — React Router + Convex variant. Covers MobX decorator setup, clientLoader bootstrap pattern, and React Router-specific wiring. Extends frontend_architecture_core.
3
- globs: apps/react-router/src/**
4
- alwaysApply: false
5
- ---
6
-
7
- # Frontend Surface Architecture — React Router + Convex
8
-
9
- This document covers the React Router-specific implementation of the surface architecture defined in `frontend_architecture_core`. Read that document first for the layer model, MobX philosophy, and component rules.
10
-
11
- ---
12
-
13
- ## Setup
14
-
15
- ### MobX
16
-
17
- MobX 2022.3 (Stage 3) decorators. Requires TypeScript 5.0+ with `experimentalDecorators` **off**.
18
-
19
- **`tsconfig.json`:**
20
- ```json
21
- { "compilerOptions": { "experimentalDecorators": false } }
22
- ```
23
-
24
- **`vite.config.ts`:**
25
- ```typescript
26
- react({ babel: { plugins: [["@babel/plugin-proposal-decorators", { version: "2023-05" }]] } })
27
- ```
28
-
29
- Rules:
30
- - Every `@observable` field must use the `accessor` keyword
31
- - No `makeObservable` or `makeAutoObservable` needed when using decorators
32
- - Direct observable mutations inside a Promise `.then` must be wrapped in an action — `@action` decorated methods satisfy this requirement, so no additional `runInAction` is needed
33
-
34
- ### Convex Client
35
-
36
- The `ConvexReactClient` singleton is exported from the provider so presenters can call mutations directly outside of React:
37
-
38
- ```typescript
39
- // src/providers/api_auth_provider.tsx
40
- export const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
41
- ```
42
-
43
- Rules:
44
- - Presenters import `convex` from `@/web/providers/api_auth_provider` to call `convex.mutation()` directly
45
- - `create.tsx` files use `useQuery(convexQuery(...))` and `useMutation({ mutationFn: useConvexMutation(...) })` for reactive data and hook-based mutations
46
-
47
- ### TanStack Query client for `clientLoader`
48
-
49
- Export a **module singleton** `queryClient` from the same provider module that wires `ConvexQueryClient` (e.g. `api_auth_provider.tsx`). Dashboard and page `clientLoader` functions import it and call `queryClient.ensureQueryData(convexQuery(...))` so bootstrap can warm the cache before render.
50
-
51
- ---
52
-
53
- ## App routing: `(auth)` vs `(dashboard)`
54
-
55
- Mirror the filesystem shape: **`routes/(auth)/…`** and **`routes/(dashboard)/…`**. Parentheses are route-group folders only; **URLs are declared in `routes.ts`**, not by folder names.
56
-
57
- **`routes.ts` shape:**
58
-
59
- ```typescript
60
- import { index, layout, prefix, type RouteConfig, route } from "@react-router/dev/routes";
61
-
62
- export default [
63
- layout("./routes/(auth)/layout.tsx", [
64
- route("sign-in/*", "./routes/(auth)/sign-in/index.tsx"),
65
- ]),
66
- layout("./routes/(dashboard)/layout.tsx", [
67
- index("./routes/(dashboard)/index.tsx"),
68
- ...prefix("todos", [
69
- index("./routes/(dashboard)/todos/index.tsx"),
70
- route(":id", "./routes/(dashboard)/todos/[id].tsx"),
71
- ]),
72
- ]),
73
- ] satisfies RouteConfig;
74
- ```
75
-
76
- - One **`layout()`** for `(auth)` (sign-in only, no sidebar).
77
- - One **`layout()`** for `(dashboard)` with `index` + `prefix("todos", …)`.
78
-
79
- | Group | Role |
80
- | --- | --- |
81
- | `(auth)` | Sign-in pages. **No** sidebar. Optional `(auth)/layout.tsx` for centered chrome. |
82
- | `(dashboard)` | **`clientLoader`** validates session (e.g. `ensureQueryData` on an auth-gated Convex query; on failure `redirect("/sign-in")`). Renders `SidebarProvider` + **Sidebar** surface + `SidebarInset` + `<Outlet />`. |
83
-
84
- **Root** (`root.tsx`): providers + `<Outlet />` only — **no** sidebar and **no** per-route auth gate here.
85
-
86
- ---
87
-
88
- ## Shell surface vs page surfaces
89
-
90
- - **Shell:** `surfaces/sidebar/` — mounted only from `(dashboard)/layout.tsx`. **`SidebarLayout`** + **`installSidebar`** + lazy section creates (same mental model as Next).
91
- - **Pages:** `surfaces/home/`, `surfaces/todos/all_todos/`, `surfaces/todos/single_todo/` — each route file imports `bootstrap*` and mounts the surface entry component with `loaderData`.
92
-
93
- Naming matches `frontend_architecture_core`: folders **`all_todos`**, **`single_todo`**; components **`Home`**, **`AllTodos`**, **`SingleTodo`** (optional `Surface` suffix if used consistently).
94
-
95
- ---
96
-
97
- ## Sidebar surface (layout chrome)
98
-
99
- Same structure as the Next.js rule doc: `sidebar.tsx` (entry), `layout.tsx` (`SidebarLayout` / `createSidebarLayout`), `install.tsx`, section folders with `create.tsx` + dumb `*.tsx`. **Slots are product-dependent.**
100
-
101
- **Install timing:** Layout-mounted sidebar uses **`useEffect`** to call `installSidebar` once. **Route-mounted** surfaces use the **synchronous `useRef` install guard**.
102
-
103
- ---
104
-
105
- ## Route → surface map
106
-
107
- | URL | Mount |
108
- | --- | --- |
109
- | `/` | `Home` |
110
- | `/todos` | `AllTodos` |
111
- | `/todos/:id` | `SingleTodo` |
112
- | `(dashboard)` layout | `Sidebar` |
113
-
114
- ---
115
-
116
- ## Dashboard layout `clientLoader` (auth gate)
117
-
118
- ```typescript
119
- import { redirect } from "react-router";
120
-
121
- import { convexQuery } from "@convex-dev/react-query";
122
-
123
- import { api } from "@/api/_generated/api";
124
- import type { Route } from "@/router/app/routes/(dashboard)/+types/layout";
125
- import { queryClient } from "@/web/providers/api_auth_provider";
126
-
127
- export async function clientLoader(_args: Route.ClientLoaderArgs) {
128
- try {
129
- await queryClient.ensureQueryData(convexQuery(api.todos.crud.listTodos, {}));
130
- } catch {
131
- return redirect("/sign-in");
132
- }
133
- return null;
134
- }
135
- ```
136
-
137
- Rules:
138
-
139
- - Use a **real authenticated Convex query** (or dedicated `api.user.me`-style query) so unauthenticated users fail fast.
140
- - Import **`queryClient`** from the provider module — do not instantiate a new `QueryClient` in the loader.
141
-
142
- ---
143
-
144
- ## Route + Bootstrap Pattern
145
-
146
- Each route that mounts a surface uses a **bootstrap** to validate params, handle redirects, and optionally **warm the TanStack Query cache** via `queryClient.ensureQueryData(convexQuery(...))` (import the **`queryClient` singleton** from `@/web/providers/api_auth_provider`). The bootstrap return value is passed into the surface as `bootstrap`; section `create.tsx` files spread `initialData: bootstrap.*` into `useQuery` so the first paint matches the server-loaded data.
147
-
148
- ### Router Types
149
-
150
- The `@/router` alias maps to `./apps/react-router/.react-router/types/*`. After `react-router typegen`, import generated types from paths that mirror the filesystem, e.g.:
151
-
152
- - `@/router/app/routes/(dashboard)/+types/layout` — dashboard layout `clientLoader`
153
- - `@/router/app/routes/(dashboard)/todos/+types/[id]` — todo detail route
154
-
155
- Use:
156
-
157
- - `Route.ClientLoaderArgs` — params, request, etc. for `clientLoader`
158
- - `Route.MetaArgs` — includes `loaderData` from `clientLoader`
159
- - `Route.ComponentProps` — includes `loaderData` for the page component
160
-
161
- ### Route File (`routes/(dashboard)/todos/[id].tsx`)
162
-
163
- ```typescript
164
- import type { Route } from "@/router/app/routes/(dashboard)/todos/+types/[id]";
165
- import { bootstrapSingleTodo } from "@/web/surfaces/todos/single_todo/bootstrap";
166
- import { SingleTodo } from "@/web/surfaces/todos/single_todo/single_todo";
167
-
168
- export async function clientLoader({ params }: Route.ClientLoaderArgs) {
169
- return bootstrapSingleTodo({ params });
170
- }
171
-
172
- export function meta({ loaderData: bootstrap }: Route.MetaArgs) {
173
- return [
174
- { title: "Todo — App Name" },
175
- { name: "description", content: "" },
176
- ];
177
- }
178
-
179
- export default function SingleTodoPage({ loaderData: bootstrap }: Route.ComponentProps) {
180
- return <SingleTodo bootstrap={bootstrap} />;
181
- }
182
- ```
183
-
184
- Rules:
185
- - Types come from `@/router/app/routes/(dashboard)/…/+types/…` — React Router generates these from the route module path
186
- - `clientLoader` runs before render; bootstrap is the single source of initial context
187
- - `meta` receives `loaderData` (bootstrap output) for document title and meta tags
188
- - Component receives `loaderData` as `bootstrap` and passes it into the surface
189
-
190
- ---
191
-
192
- ## Layer Reference (React Router-specific)
193
-
194
- ### `bootstrap.ts`
195
-
196
- Validates params, handles auth redirects, and pre-populates the TanStack Query cache with the primary entity. The fetched data is passed as `initialData` to queries in the `create` layer.
197
-
198
- ```typescript
199
- import { redirect } from "react-router";
200
-
201
- import { convexQuery } from "@convex-dev/react-query";
202
- import type { Route } from "@/router/app/routes/(dashboard)/todos/+types/[id]";
203
- import type { TodoId } from "@/api/todos/types";
204
- import { api } from "@/api/_generated/api";
205
- import { queryClient } from "@/web/providers/api_auth_provider";
206
-
207
- export type SingleTodoBootstrapArgs = Pick<Route.ClientLoaderArgs, "params">;
208
- export type SingleTodoBootstrap = Awaited<ReturnType<typeof bootstrapSingleTodo>>;
209
-
210
- export const bootstrapSingleTodo = async ({ params }: SingleTodoBootstrapArgs) => {
211
- if (!params.id) {
212
- throw redirect("/todos");
213
- }
214
- const todoId = params.id as TodoId;
215
- const todo = await queryClient.ensureQueryData(
216
- convexQuery(api.todos.crud.getTodo, { todoId }),
217
- );
218
- return { todoId, todo };
219
- };
220
- ```
221
-
222
- Rules:
223
- - Use `queryClient.ensureQueryData` with `convexQuery` — pre-populates the TanStack Query cache
224
- - Import **`queryClient`** from `@/web/providers/api_auth_provider` (singleton shared with `QueryClientProvider`)
225
- - Auth failures/redirects belong here, not in components
226
-
227
- ### `[feature].tsx` — Surface Entry Component
228
-
229
- No `"use client"` directive — React Router is fully client-side. **Does not instantiate controllers** — that happens in `install.tsx`.
230
-
231
- ```typescript
232
- import { useMemo, useRef } from "react";
233
- import { useNavigate } from "react-router";
234
-
235
- import type { SingleTodoBootstrap } from "@/web/surfaces/todos/single_todo/bootstrap";
236
- import { installSingleTodo } from "@/web/surfaces/todos/single_todo/install";
237
- import { createLayout } from "@/web/surfaces/todos/single_todo/layout";
238
-
239
- export type SingleTodoProps = {
240
- bootstrap: SingleTodoBootstrap;
241
- };
242
-
243
- export const SingleTodo = ({ bootstrap }: SingleTodoProps) => {
244
- const navigate = useNavigate();
245
- const { layout, Layout } = useMemo(() => createLayout(), []);
246
- const installed = useRef(false);
247
-
248
- if (!installed.current) {
249
- installed.current = true;
250
- installSingleTodo({ layout, bootstrap, navigate });
251
- }
252
-
253
- return <Layout />;
254
- };
255
- ```
256
-
257
- Rules:
258
- - Use `useNavigate` from `react-router`
259
- - No `"use client"` directive needed
260
- - **Never instantiate controllers here** — pass deps to install, which creates the controller
261
- - All imports use `@/` aliases — no relative imports
262
-
263
- ### `install.tsx`
264
-
265
- ```typescript
266
- import type { NavigateFunction } from "react-router";
267
-
268
- import type { SingleTodoBootstrap } from "@/web/surfaces/todos/single_todo/bootstrap";
269
- import type { SingleTodoLayoutController } from "@/web/surfaces/todos/single_todo/layout";
270
- import { SingleTodoController } from "@/web/surfaces/todos/single_todo/single_todo_controller";
271
-
272
- export type InstallSingleTodoOpts = {
273
- layout: SingleTodoLayoutController;
274
- bootstrap: SingleTodoBootstrap;
275
- navigate: NavigateFunction;
276
- };
277
-
278
- export const installSingleTodo = ({ layout, bootstrap, navigate }: InstallSingleTodoOpts) => {
279
- const controller = new SingleTodoController();
280
-
281
- import("@/web/surfaces/todos/single_todo/header/create").then(({ createHeader }) => {
282
- layout.setHeader(createHeader({ navigate }));
283
- });
284
-
285
- import("@/web/surfaces/todos/single_todo/main/create").then(({ createMain }) => {
286
- layout.setMain(createMain({ controller, bootstrap }));
287
- });
288
- };
289
- ```
290
-
291
- Rules:
292
- - Use `NavigateFunction` from `react-router` for the navigate type
293
- - **Controller instantiated here** — exactly once
294
- - Dynamic `import()` paths use `@/` aliases
295
-
296
- ### `layout.tsx`
297
-
298
- ```typescript
299
- import { action, observable } from "mobx";
300
- import { observer } from "mobx-react-lite";
301
- import type { ComponentType } from "react";
302
-
303
- import { Skeleton } from "@/ui/base/skeleton";
304
-
305
- export class LayoutController {
306
- @observable.ref
307
- accessor Header: ComponentType | undefined = undefined;
308
-
309
- @observable.ref
310
- accessor Main: ComponentType | undefined = undefined;
311
-
312
- @action
313
- setHeader(Header: ComponentType) {
314
- this.Header = Header;
315
- }
316
-
317
- @action
318
- setMain(Main: ComponentType) {
319
- this.Main = Main;
320
- }
321
- }
322
-
323
- export const createLayout = () => {
324
- const layout = new LayoutController();
325
- return {
326
- layout,
327
- Layout: observer(() => {
328
- const Header = layout.Header;
329
- const Main = layout.Main;
330
- return (
331
- <div className="flex flex-col gap-8 w-full">
332
- {Header ? <Header /> : <Skeleton className="h-12" />}
333
- {Main ? <Main /> : <Skeleton className="h-64" />}
334
- </div>
335
- );
336
- }),
337
- };
338
- };
339
- ```
340
-
341
- Rules:
342
- - Use `@observable.ref accessor` for component slots
343
- - `@action` decorated methods create an action boundary — calling them from Promise `.then` is safe without additional `runInAction`
344
-
345
- ### `[feature]_controller.ts`
346
-
347
- ```typescript
348
- import { action, computed, observable } from "mobx";
349
-
350
- import type { TodoId } from "@/api/todos/types";
351
-
352
- export class SingleTodoController {
353
- @observable.ref
354
- accessor isEditOpen = false;
355
-
356
- @observable.ref
357
- accessor selectedTodoId: TodoId | undefined = undefined;
358
-
359
- @computed
360
- get editTitle() {
361
- return this.selectedTodoId ? "Edit Todo" : "New Todo";
362
- }
363
-
364
- @action
365
- openEdit(todoId: TodoId) {
366
- this.selectedTodoId = todoId;
367
- this.isEditOpen = true;
368
- }
369
-
370
- @action
371
- closeEdit() {
372
- this.isEditOpen = false;
373
- this.selectedTodoId = undefined;
374
- }
375
- }
376
- ```
377
-
378
- ### `[section]_presenter.ts`
379
-
380
- ```typescript
381
- import { action, computed, observable } from "mobx";
382
- import type { NavigateFunction } from "react-router";
383
-
384
- import { api } from "@/api/_generated/api";
385
- import { convex } from "@/web/providers/api_auth_provider";
386
-
387
- export type NewTodoPresenterOpts = {
388
- navigate: NavigateFunction;
389
- };
390
-
391
- export class NewTodoPresenter {
392
- private readonly navigate: NavigateFunction;
393
-
394
- constructor({ navigate }: NewTodoPresenterOpts) {
395
- this.navigate = navigate;
396
- }
397
-
398
- @observable
399
- accessor input = "";
400
-
401
- @observable
402
- accessor isPending = false;
403
-
404
- @computed
405
- get isSubmitDisabled() {
406
- return !this.input.trim() || this.isPending;
407
- }
408
-
409
- @action
410
- setInput(value: string) {
411
- this.input = value;
412
- }
413
-
414
- @action
415
- async submit() {
416
- this.isPending = true;
417
- try {
418
- await convex.mutation(api.todos.crud.createTodo, { title: this.input.trim() });
419
- this.input = "";
420
- } finally {
421
- this.isPending = false;
422
- }
423
- }
424
- }
425
- ```
426
-
427
- Rules:
428
- - `@action` decorated methods satisfy the action boundary — mutations inside an async `@action` method are automatically tracked, no `runInAction` needed
429
-
430
- ### `[section]/create.tsx`
431
-
432
- ```typescript
433
- import { convexQuery } from "@convex-dev/react-query";
434
- import { useQuery } from "@tanstack/react-query";
435
- import { observer } from "mobx-react-lite";
436
-
437
- import { api } from "@/api/_generated/api";
438
- import type { SingleTodoBootstrap } from "@/web/surfaces/todos/single_todo/bootstrap";
439
- import type { SingleTodoController } from "@/web/surfaces/todos/single_todo/single_todo_controller";
440
- import { Main } from "@/web/surfaces/todos/single_todo/main/main";
441
-
442
- export type CreateMainOpts = {
443
- controller: SingleTodoController;
444
- bootstrap: SingleTodoBootstrap;
445
- };
446
-
447
- export const createMain = ({ controller, bootstrap }: CreateMainOpts) => {
448
- return observer(() => {
449
- const { data: todo } = useQuery({
450
- initialData: bootstrap.todo,
451
- ...convexQuery(api.todos.crud.getTodo, { todoId: bootstrap.todoId }),
452
- });
453
-
454
- return (
455
- <Main
456
- todo={todo}
457
- onToggle={(isCompleted) =>
458
- controller.updateTodo({
459
- todoId: bootstrap.todoId,
460
- isCompleted,
461
- })
462
- }
463
- onEdit={() => controller.openEdit(bootstrap.todoId)}
464
- />
465
- );
466
- });
467
- };
468
- ```
469
-
470
- Rules:
471
- - No `"use client"` directive needed
472
- - Spread `convexQuery` with `initialData: bootstrap.[entity]` — eliminates loading flash, query stays reactive
473
- - All imports use `@/` aliases — no relative imports
@@ -1,29 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- pull_request:
8
-
9
- permissions:
10
- actions: read
11
- contents: read
12
-
13
- jobs:
14
- main:
15
- name:
16
- runs-on: ubuntu-latest
17
- steps:
18
- - uses: actions/checkout@v4
19
- with:
20
- fetch-depth: 0
21
-
22
- - uses: oven-sh/setup-bun@v1
23
- with:
24
- bun-version: latest
25
-
26
- # - run: bun install --no-cache
27
- # - uses: nrwl/nx-set-shas@v4
28
-
29
- # - run: bun nx affected -t build
@@ -1,23 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "compilerOptions": {
4
- /* These settings are not required by Convex and can be modified. */
5
- "allowJs": true,
6
- "strict": true,
7
- "moduleResolution": "Bundler",
8
- "noUnusedLocals": true,
9
- "jsx": "react-jsx",
10
- "skipLibCheck": true,
11
- "allowSyntheticDefaultImports": true,
12
- /* These compiler options are required by Convex */
13
- "target": "ESNext",
14
- "lib": ["ES2021", "dom"],
15
- "forceConsistentCasingInFileNames": true,
16
- "module": "ESNext",
17
- "isolatedModules": true,
18
- "noEmit": true
19
- },
20
- "exclude": ["node_modules", "./_generated", "CODEOWNERS"],
21
- "extends": "../../tsconfig.base.json",
22
- "include": ["**/*.ts", "**/*.js", "**/*.json"]
23
- }
@@ -1,10 +0,0 @@
1
- # Backend
2
- CONVEX_DEPLOYMENT=
3
- CLERK_SECRET_KEY=
4
- CONVEX_SITE_URL= # HTTPS URL
5
- CLERK_JWT_ISSUER_DOMAIN=
6
- RESEND_API_KEY=
7
-
8
- # Frontend
9
- NEXT_PUBLIC_CONVEX_URL=
10
- NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
@@ -1,6 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- declare module '*.svg' {
3
- const content: any;
4
- export const ReactComponent: any;
5
- export default content;
6
- }
@@ -1,5 +0,0 @@
1
- /// <reference types="next" />
2
- /// <reference types="next/image-types/global" />
3
-
4
- // NOTE: This file should not be edited
5
- // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -1,22 +0,0 @@
1
- //@ts-check
2
-
3
- // eslint-disable-next-line @typescript-eslint/no-var-requires
4
- const { composePlugins, withNx } = require("@nx/next");
5
-
6
- /**
7
- * @type {import('@nx/next/plugins/with-nx').WithNxOptions}
8
- **/
9
- const nextConfig = {
10
- nx: {
11
- // Set this to true if you would like to use SVGR
12
- // See: https://github.com/gregberge/svgr
13
- svgr: false,
14
- },
15
- };
16
-
17
- const plugins = [
18
- // Add more Next.js plugins to this list if needed.
19
- withNx,
20
- ];
21
-
22
- module.exports = composePlugins(...plugins)(nextConfig);
@@ -1,17 +0,0 @@
1
- /*
2
- * Copyright (c) Aron Weston 2025.
3
- */
4
-
5
- const { join } = require("node:path");
6
-
7
- // Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build
8
- // option from your application's configuration (i.e. project.json).
9
- //
10
- // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries
11
-
12
- module.exports = {
13
- plugins: {
14
- "@tailwindcss/postcss": {},
15
- autoprefixer: {},
16
- },
17
- };
@@ -1,22 +0,0 @@
1
- {
2
- "name": "nextjs",
3
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
- "sourceRoot": "apps/nextjs",
5
- "projectType": "application",
6
- "tags": [],
7
- "targets": {
8
- "dev": {
9
- "executor": "nx:run-commands",
10
- "options": {
11
- "command": "bunx next dev apps/nextjs --port 5001"
12
- }
13
- },
14
- "build": {
15
- "executor": "@nx/next:build",
16
- "options": {
17
- "root": "apps/nextjs",
18
- "outputPath": "dist/apps/nextjs"
19
- }
20
- }
21
- }
22
- }
@@ -1,21 +0,0 @@
1
- /*
2
- * Copyright (c) Aron Weston 2025.
3
- */
4
-
5
- import "../app.css";
6
-
7
- import { Toaster } from "sonner";
8
-
9
-
10
- type RootLayoutProps = {
11
- children: React.ReactNode;
12
- };
13
-
14
- export default function AuthLayout({ children }: RootLayoutProps) {
15
- return (
16
- <main className="flex min-h-screen flex-col justify-center items-center overflow-hidden">
17
- {children}
18
- <Toaster />
19
- </main>
20
- );
21
- }
@@ -1,23 +0,0 @@
1
- /*
2
- * Copyright (c) Aron Weston 2025.
3
- */
4
-
5
- import type { Metadata } from "next";
6
- import Link from "next/link";
7
-
8
- export const metadata: Metadata = {
9
- title: "Not Allowed",
10
- description: "You are not allowed to access this page.",
11
- };
12
-
13
- export default async function NotAllowed() {
14
- return (
15
- <div className="flex flex-col items-center justify-center h-screen">
16
- <h1 className="text-2xl font-bold">Not Allowed</h1>
17
- <p className="text-sm text-gray-500">You are not allowed to access this page.</p>
18
- <Link href="/sign-in" className="text-blue-500">
19
- Continue to login page
20
- </Link>
21
- </div>
22
- );
23
- }
@@ -1,15 +0,0 @@
1
- /*
2
- * Copyright (c) Aron Weston 2025.
3
- */
4
-
5
- import { SignIn } from "@clerk/nextjs";
6
- import type { Metadata } from "next";
7
-
8
- export const metadata: Metadata = {
9
- title: "Sign In",
10
- description: "Sign In to your account",
11
- };
12
-
13
- export default function LoginPage() {
14
- return <SignIn withSignUp={true} />;
15
- }