create-aron-app 0.1.7 → 0.1.8
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.
- package/README.md +24 -31
- package/dist/index.js +38 -49
- package/package.json +3 -3
- package/templates/.cursor/rules/backend.mdc +112 -0
- package/templates/.cursor/rules/coding_standards.mdc +85 -4
- package/templates/.cursor/rules/frontend_architecture.mdc +334 -0
- package/templates/.github/workflows/ci.yml +17 -6
- package/templates/apps/{react-router → web}/.react-router/types/+routes.ts +11 -6
- 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
- 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
- package/templates/apps/web/.react-router/types/src/routes/(dashboard)/(todos)/+types/layout.ts +65 -0
- package/templates/apps/{react-router → web}/project.json +11 -4
- package/templates/apps/{react-router → web}/react-router.config.ts +1 -1
- package/templates/apps/web/src/libs/convex_query_client.ts +11 -0
- package/templates/apps/web/src/libs/react_query_client.ts +17 -0
- package/templates/apps/web/src/libs/server/auth.ts +32 -0
- package/templates/apps/web/src/libs/server/protected.ts +17 -0
- package/templates/apps/web/src/providers/api_auth_provider.tsx +26 -0
- package/templates/apps/web/src/providers/global_provider.tsx +28 -0
- package/templates/apps/web/src/providers/navigation_loading_bar_provider.tsx +72 -0
- package/templates/apps/web/src/root.tsx +68 -0
- package/templates/apps/web/src/routes/(dashboard)/(todos)/[id].tsx +33 -0
- package/templates/apps/web/src/routes/(dashboard)/(todos)/index.tsx +26 -0
- package/templates/apps/web/src/routes/(dashboard)/(todos)/layout.tsx +9 -0
- package/templates/apps/{react-router → web}/src/routes/(dashboard)/index.tsx +3 -2
- package/templates/apps/web/src/routes/(dashboard)/layout.tsx +20 -0
- package/templates/apps/{react-router → web}/src/routes.ts +4 -2
- package/templates/apps/{nextjs → web}/src/surfaces/sidebar/install.tsx +1 -5
- package/templates/apps/{react-router → web}/src/surfaces/sidebar/layout.tsx +24 -15
- package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/all_todos.tsx +1 -1
- package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/all_todos_controller.ts +1 -1
- package/templates/apps/web/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
- package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/bootstrap.ts +4 -5
- package/templates/apps/{nextjs → web}/src/surfaces/todos/single_todo/header/create.tsx +4 -6
- package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/single_todo_controller.ts +1 -1
- package/templates/biome.json +5 -0
- package/templates/bun.lock +11 -1281
- package/templates/nx.json +0 -11
- package/templates/package.json +2 -3
- package/templates/shared/assets/src/styles/global.css +14 -8
- package/templates/shared/ui/src/base/collapsible.tsx +31 -0
- package/templates/shared/ui/src/base/hover-card.tsx +42 -0
- package/templates/shared/ui/src/base/input-group.tsx +168 -0
- package/templates/shared/ui/src/base/panel.tsx +93 -0
- package/templates/shared/ui/src/hooks/use_mobile.tsx +1 -1
- package/templates/shared/ui/src/hooks/use_query_params.tsx +6 -7
- package/templates/shared/ui/tsconfig.json +1 -1
- package/templates/shared/utils/src/convex.ts +2 -1
- package/templates/tsconfig.base.json +2 -4
- package/templates/.cursor/commands/builder.md +0 -0
- package/templates/.cursor/rules/api_architecture.mdc +0 -262
- package/templates/.cursor/rules/convex_rules.mdc +0 -331
- package/templates/.cursor/rules/frontend_architecture_core.mdc +0 -495
- package/templates/.cursor/rules/frontend_architecture_nextjs.mdc +0 -458
- package/templates/.cursor/rules/frontend_architecture_reactrouter.mdc +0 -473
- package/templates/apps/nextjs/.env.example +0 -10
- package/templates/apps/nextjs/index.d.ts +0 -6
- package/templates/apps/nextjs/next-env.d.ts +0 -5
- package/templates/apps/nextjs/next.config.js +0 -22
- package/templates/apps/nextjs/postcss.config.js +0 -17
- package/templates/apps/nextjs/project.json +0 -22
- package/templates/apps/nextjs/src/app/(auth)/layout.tsx +0 -21
- package/templates/apps/nextjs/src/app/(auth)/not-allowed/page.tsx +0 -23
- package/templates/apps/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -15
- package/templates/apps/nextjs/src/app/(dashboard)/layout.tsx +0 -22
- package/templates/apps/nextjs/src/app/(dashboard)/page.tsx +0 -12
- package/templates/apps/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +0 -26
- package/templates/apps/nextjs/src/app/(dashboard)/todos/page.tsx +0 -19
- package/templates/apps/nextjs/src/app/app.css +0 -3
- package/templates/apps/nextjs/src/app/layout.tsx +0 -26
- package/templates/apps/nextjs/src/middleware.ts +0 -18
- package/templates/apps/nextjs/src/providers/convex_provider.tsx +0 -44
- package/templates/apps/nextjs/src/surfaces/home/home.tsx +0 -27
- package/templates/apps/nextjs/src/surfaces/home/layout.tsx +0 -44
- package/templates/apps/nextjs/src/surfaces/home/main/create.tsx +0 -34
- package/templates/apps/nextjs/src/surfaces/sidebar/layout.tsx +0 -118
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/create.tsx +0 -19
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_config.ts +0 -22
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_main.tsx +0 -25
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/create.tsx +0 -21
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +0 -33
- package/templates/apps/nextjs/src/surfaces/sidebar/sidebar.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/sidebar/ui/sidebar_nav_link.tsx +0 -39
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/create.tsx +0 -28
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/user_menu.tsx +0 -42
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos.tsx +0 -29
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos_controller.ts +0 -61
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/bootstrap.ts +0 -21
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/header/create.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/install.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/layout.tsx +0 -44
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/create.tsx +0 -49
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/main.tsx +0 -70
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +0 -56
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +0 -99
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/bootstrap.ts +0 -32
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/header.tsx +0 -22
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/install.tsx +0 -27
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/layout.tsx +0 -55
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/create.tsx +0 -38
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/main.tsx +0 -49
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo.tsx +0 -29
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo_controller.ts +0 -13
- package/templates/apps/nextjs/src/utils/auth.ts +0 -18
- package/templates/apps/nextjs/src/utils/convex.ts +0 -11
- package/templates/apps/nextjs/src/utils/font.ts +0 -9
- package/templates/apps/nextjs/tsconfig.json +0 -42
- package/templates/apps/react-router/src/providers/api_auth_provider.tsx +0 -40
- package/templates/apps/react-router/src/root.tsx +0 -37
- package/templates/apps/react-router/src/routes/(dashboard)/layout.tsx +0 -37
- package/templates/apps/react-router/src/routes/(dashboard)/todos/[id].tsx +0 -19
- package/templates/apps/react-router/src/routes/(dashboard)/todos/index.tsx +0 -19
- package/templates/apps/react-router/src/surfaces/home/bootstrap.ts +0 -9
- package/templates/apps/react-router/src/surfaces/home/install.tsx +0 -17
- package/templates/apps/react-router/src/surfaces/sidebar/install.tsx +0 -23
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/create.tsx +0 -21
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +0 -31
- package/templates/apps/react-router/src/surfaces/todos/all_todos/bootstrap.ts +0 -18
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +0 -11
- package/templates/apps/react-router/src/surfaces/todos/single_todo/header/create.tsx +0 -32
- /package/templates/apps/{react-router → web}/.env.example +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/+future.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/+server-build.d.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/+types/root.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(auth)/+types/layout.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(dashboard)/+types/index.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(dashboard)/+types/layout.ts +0 -0
- /package/templates/apps/{react-router → web}/postcss.config.js +0 -0
- /package/templates/apps/{react-router → web}/public/favicon.ico +0 -0
- /package/templates/apps/{react-router → web}/src/app.css +0 -0
- /package/templates/apps/{react-router → web}/src/components/error_boundary.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/routes/(auth)/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/routes/(auth)/sign-in/index.tsx +0 -0
- /package/templates/apps/{nextjs → web}/src/surfaces/home/bootstrap.ts +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/home/home.tsx +0 -0
- /package/templates/apps/{nextjs → web}/src/surfaces/home/install.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/home/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/home/main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/nav_main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/nav_main/nav_main.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/sidebar.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/user_menu/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/user_menu/user_menu.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/header/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/install.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/main.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +0 -0
- /package/templates/apps/{nextjs → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/header/header.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/install.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/main/main.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/single_todo.tsx +0 -0
- /package/templates/apps/{react-router → web}/tsconfig.json +0 -0
- /package/templates/apps/{react-router → web}/vite.config.ts +0 -0
|
@@ -22,6 +22,22 @@ globs: **/*.ts,**/*.tsx
|
|
|
22
22
|
- Use singular for single items (`todo`, `entity`) and plural for collections (`todos`, `entities`)
|
|
23
23
|
- Destructure to extract IDs: `const { entityId, ...updates } = args;`
|
|
24
24
|
|
|
25
|
+
### Zod Schemas
|
|
26
|
+
|
|
27
|
+
The inferred type and the schema constant must share the same PascalCase name. Declare the type **above** the const. This applies to both API and web code:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// Good
|
|
31
|
+
export type CreateConversationInput = z.infer<typeof CreateConversationInput>;
|
|
32
|
+
export const CreateConversationInput = z.object({
|
|
33
|
+
title: z.string().min(1).max(120),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Bad — mismatched names, type below const
|
|
37
|
+
export const createConversationSchema = z.object({ title: z.string() });
|
|
38
|
+
export type CreateConversationInput = z.infer<typeof createConversationSchema>;
|
|
39
|
+
```
|
|
40
|
+
|
|
25
41
|
## Code Style
|
|
26
42
|
|
|
27
43
|
### Formatting
|
|
@@ -30,6 +46,73 @@ globs: **/*.ts,**/*.tsx
|
|
|
30
46
|
- 2-space indentation
|
|
31
47
|
- Trailing commas in multi-line arrays and objects
|
|
32
48
|
|
|
49
|
+
### Class Spacing
|
|
50
|
+
|
|
51
|
+
Each member of a class (property, accessor, or method) must be separated by a blank line. Decorators go on their own line above the member. Never inline a method body — always expand to a full block:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Good
|
|
55
|
+
export class FooController {
|
|
56
|
+
@observable.ref
|
|
57
|
+
accessor isOpen = false;
|
|
58
|
+
|
|
59
|
+
@observable.ref
|
|
60
|
+
accessor selectedId: string | undefined = undefined;
|
|
61
|
+
|
|
62
|
+
@computed
|
|
63
|
+
get label() {
|
|
64
|
+
return this.isOpen ? "Close" : "Open";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@action
|
|
68
|
+
open(id: string) {
|
|
69
|
+
this.selectedId = id;
|
|
70
|
+
this.isOpen = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Bad — inlined bodies, missing blank lines, stacked decorator
|
|
75
|
+
export class FooController {
|
|
76
|
+
@observable.ref accessor isOpen = false;
|
|
77
|
+
@observable.ref accessor selectedId: string | undefined = undefined;
|
|
78
|
+
@computed get label() { return this.isOpen ? "Close" : "Open"; }
|
|
79
|
+
@action open(id: string) { this.selectedId = id; this.isOpen = true; }
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### No Expression Nesting
|
|
84
|
+
|
|
85
|
+
Never pass an awaited call or a complex expression directly as an argument. Extract to a named variable first:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Good
|
|
89
|
+
const user = await getUser(userId);
|
|
90
|
+
await sendWelcomeEmail(user);
|
|
91
|
+
|
|
92
|
+
// Bad
|
|
93
|
+
await sendWelcomeEmail(await getUser(userId));
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Block Statements
|
|
97
|
+
|
|
98
|
+
Always use block bodies (`{ }`). This is enforced by Biome (`useBlockStatements`). No single-line `if`/`throw`, no expression-body arrow callbacks:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Good
|
|
102
|
+
if (!conversation) {
|
|
103
|
+
throw new Error("Not found");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onSubmit={() => {
|
|
107
|
+
controller.submit({ id: bootstrap.id });
|
|
108
|
+
}}
|
|
109
|
+
|
|
110
|
+
// Bad
|
|
111
|
+
if (!conversation) throw new Error("Not found");
|
|
112
|
+
|
|
113
|
+
onSubmit={() => controller.submit({ id: bootstrap.id })}
|
|
114
|
+
```
|
|
115
|
+
|
|
33
116
|
### Conditional Logic
|
|
34
117
|
|
|
35
118
|
Prefer concise conditionals — avoid unnecessary intermediate variables:
|
|
@@ -38,25 +121,23 @@ Prefer concise conditionals — avoid unnecessary intermediate variables:
|
|
|
38
121
|
// Good
|
|
39
122
|
const count = entity.limit ?? 0;
|
|
40
123
|
if (count <= 0) {
|
|
41
|
-
throw new
|
|
124
|
+
throw new Error("Limit reached");
|
|
42
125
|
}
|
|
43
126
|
|
|
44
127
|
// Bad
|
|
45
128
|
const remaining = entity.limit ?? 0;
|
|
46
129
|
if (!entity.limit || entity.limit <= 0) {
|
|
47
|
-
throw new
|
|
130
|
+
throw new Error("Limit reached");
|
|
48
131
|
}
|
|
49
132
|
```
|
|
50
133
|
|
|
51
134
|
### Async / Await
|
|
52
135
|
- Always `await` async operations — never fire-and-forget unless intentional
|
|
53
|
-
- Chain operations when it improves readability
|
|
54
136
|
- Use `Promise.all()` for independent parallel operations
|
|
55
137
|
|
|
56
138
|
### Validation
|
|
57
139
|
- Validate business rules before any database operation
|
|
58
140
|
- Centralise reusable validation in `@/utils/`
|
|
59
|
-
- Throw `ConvexError` with clear, actionable messages
|
|
60
141
|
- Keep CRUD files thin — validation logic belongs in utils
|
|
61
142
|
|
|
62
143
|
## Testing
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Web frontend — surface architecture (MobX + React Router + Convex + Clerk SSR). Layers, routing, data loading, Convex/React Query wiring, and component conventions.
|
|
3
|
+
globs: templates/apps/web/**/*.tsx,templates/apps/web/**/*.ts,apps/web/**/*.tsx,apps/web/**/*.ts
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Frontend architecture — React Router + Convex
|
|
8
|
+
|
|
9
|
+
Audience: engineers working on `apps/web` (or `templates/apps/web` in this repo).
|
|
10
|
+
|
|
11
|
+
This stack uses **surfaces** (vertical slices), **MobX** for UI state, **Convex** for data, and **SSR loaders** with `ConvexHttpClient` + Clerk. An older **v2** ruleset documented a Hono BFF + `api` client; this project does **not** use that pattern — use Convex queries/mutations and the paths below instead.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Philosophy
|
|
16
|
+
|
|
17
|
+
1. **Display components are dumb** — props in, JSX out. No data fetching, no surface state, no controller imports.
|
|
18
|
+
2. **`create.tsx` factories are smart closures** — they close over controller/bootstrap at wiring time and return an `observer()` component. This is the only layer that bridges MobX and React hooks.
|
|
19
|
+
3. **Controllers are the brain** — a MobX class owns cross-section UI state (dialogs, selection). No React imports.
|
|
20
|
+
|
|
21
|
+
**Why explicit wiring instead of React Context?** Context hides the dependency graph. Here, access is traceable: surface → `install` → `create` → display. No surprise re-renders or ambiguous state ownership.
|
|
22
|
+
|
|
23
|
+
**Why not add Zustand?** MobX plus optional `GlobalProvider` already cover reactive UI state; Convex plus TanStack Query cover server state — avoid a third paradigm.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Layer stack
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
routes/.../[page].tsx → server loader + clientLoader; passes loaderData as bootstrap
|
|
31
|
+
bootstrap.ts → ConvexHttpClient query only (SSR); no browser APIs
|
|
32
|
+
[feature].tsx → surface entry: useRef install guard, useMemo(createLayout)
|
|
33
|
+
install.tsx → instantiate controller; dynamic import() of section creates
|
|
34
|
+
layout.tsx → LayoutController + observer shell + skeleton slots
|
|
35
|
+
[feature]_controller.ts → MobX — shared UI state; may call convex.mutation
|
|
36
|
+
[section]_presenter.ts → MobX — section-scoped logic (optional)
|
|
37
|
+
[section]/create.tsx → factory → observer; useQuery + convexQuery + initialData
|
|
38
|
+
[section]/[section].tsx → pure display (`useFormContext` exception for forms)
|
|
39
|
+
ui/ → dumb components scoped to this surface
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Folder and naming
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
src/surfaces/todos/
|
|
48
|
+
all_todos/ # list surface
|
|
49
|
+
single_todo/ # id-scoped surface
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- File names: **snake_case**
|
|
53
|
+
- Factory/install args: `Opts` suffix — `InstallSingleTodoOpts`
|
|
54
|
+
- Component props: `Props` suffix — `MainProps`
|
|
55
|
+
- List surfaces: **`All`** prefix — `AllTodos`
|
|
56
|
+
- Id surfaces: **`Single`** prefix — `SingleTodo`
|
|
57
|
+
- CRUD UI folders: `new_`, `edit_`, `delete_` — never `create_` (conflicts with `create.tsx` pattern)
|
|
58
|
+
- Display component names must **not** repeat the parent path — inside `single_todo/header/`, export `Header`, not `SingleTodoHeader`
|
|
59
|
+
- Imports: **`@/...` only** — no relative imports within features
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Setup — MobX (Stage 3)
|
|
64
|
+
|
|
65
|
+
Requires TypeScript 5+ with `experimentalDecorators: false` and Babel decorators in Vite:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{ "compilerOptions": { "experimentalDecorators": false } }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
react({ babel: { plugins: [["@babel/plugin-proposal-decorators", { version: "2023-05" }]] } })
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Use `accessor` on `@observable` fields
|
|
76
|
+
- Prefer `@action` methods — mutations after `await` inside an `@action async` are safe without `runInAction`
|
|
77
|
+
- Use `@observable.ref` for component slot fields on layout controllers
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Setup — Convex and React Query
|
|
82
|
+
|
|
83
|
+
**Convex React client** (browser, `expectAuth: true`) and **ConvexQueryClient** live in `@/web/libs/convex_query_client`:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { convex, convexQueryClient } from "@/web/libs/convex_query_client";
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**TanStack Query client** is `@/web/libs/react_query_client` — it connects `convexQueryClient` and sets default `queryFn`. Use this singleton anywhere you need a `QueryClient` on the client.
|
|
90
|
+
|
|
91
|
+
**Presenters and controllers** call mutations outside React via the same `convex` singleton:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { convex } from "@/web/libs/convex_query_client";
|
|
95
|
+
import { api } from "@/api/_generated/api";
|
|
96
|
+
|
|
97
|
+
await convex.mutation(api.todos.crud.updateTodo, args);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Creates** use `useQuery` with `convexQuery` from `@convex-dev/react-query` and **`initialData` from bootstrap** so the first paint matches SSR.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Global state (three tiers)
|
|
105
|
+
|
|
106
|
+
| Tier | Mechanism | When |
|
|
107
|
+
|------|-----------|------|
|
|
108
|
+
| Module `stores/` | Plain module variable | One-shot, non-reactive handoff across navigation |
|
|
109
|
+
| `GlobalProvider` | React context + `useGlobal()` | Reactive state across `<Outlet />` (use sparingly) |
|
|
110
|
+
| MobX controller | `[feature]_controller.ts` | All UI state inside a surface |
|
|
111
|
+
|
|
112
|
+
Module store rules: consume-once helpers, one concern per file, document lifetime at top, never for persistence alone.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Routing and auth
|
|
117
|
+
|
|
118
|
+
**Route config** (`src/routes.ts`) uses route groups `(auth)` and `(dashboard)`. URLs come from `layout`/`prefix`/`route`, not folder names.
|
|
119
|
+
|
|
120
|
+
Example shape (adjust files to match your app):
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { index, layout, prefix, type RouteConfig, route } from "@react-router/dev/routes";
|
|
124
|
+
|
|
125
|
+
export default [
|
|
126
|
+
layout("./routes/(auth)/layout.tsx", [route("/sign-in/*", "./routes/(auth)/sign-in/index.tsx")]),
|
|
127
|
+
layout("./routes/(dashboard)/layout.tsx", [
|
|
128
|
+
index("./routes/(dashboard)/index.tsx"),
|
|
129
|
+
...prefix("todos", [
|
|
130
|
+
layout("./routes/(dashboard)/(todos)/layout.tsx", [
|
|
131
|
+
index("./routes/(dashboard)/(todos)/index.tsx"),
|
|
132
|
+
route(":id", "./routes/(dashboard)/(todos)/[id].tsx"),
|
|
133
|
+
]),
|
|
134
|
+
]),
|
|
135
|
+
]),
|
|
136
|
+
] satisfies RouteConfig;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Middleware** (`root.tsx`): Clerk + a protected-route middleware enforce auth for non-public paths before loaders run.
|
|
140
|
+
|
|
141
|
+
**`root.tsx`**: providers (`ApiAuthProvider`, `GlobalProvider`, …) + `<Outlet />` — no feature sidebar here.
|
|
142
|
+
|
|
143
|
+
**Dashboard layout**: shell only (e.g. `SidebarProvider`, `Sidebar`, `Outlet`).
|
|
144
|
+
|
|
145
|
+
**Router types**: import `Route` from `./+types/...` adjacent to the route file (or the generated types path your tsconfig uses).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## SSR data loading (loaders + bootstrap)
|
|
150
|
+
|
|
151
|
+
Flow:
|
|
152
|
+
|
|
153
|
+
1. **Server** — route `loader` builds an authenticated `ConvexHttpClient` and runs bootstrap.
|
|
154
|
+
2. **Client** — `clientLoader` returns `serverLoader()` only — no second fetch.
|
|
155
|
+
3. **Hydration** — `useQuery({ ...convexQuery(...), initialData: bootstrap.* })` so the live subscription takes over.
|
|
156
|
+
|
|
157
|
+
### Auth helper — `src/libs/server/auth.ts`
|
|
158
|
+
|
|
159
|
+
Single place for loader-time Convex HTTP client + redirect if unauthenticated:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { getAuth } from "@clerk/react-router/server";
|
|
163
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
164
|
+
import type { LoaderFunctionArgs } from "react-router";
|
|
165
|
+
import { redirect } from "react-router";
|
|
166
|
+
|
|
167
|
+
export const createConvexClient = async (args: LoaderFunctionArgs): Promise<ConvexHttpClient> => {
|
|
168
|
+
const auth = await getAuth(args);
|
|
169
|
+
if (!auth.isAuthenticated) {
|
|
170
|
+
throw redirect(`/sign-in?redirect_url=${encodeURIComponent(args.request.url)}`);
|
|
171
|
+
}
|
|
172
|
+
const token = await auth.getToken({ template: "convex" });
|
|
173
|
+
const client = new ConvexHttpClient(process.env.VITE_CONVEX_URL!);
|
|
174
|
+
if (token) {
|
|
175
|
+
client.setAuth(token);
|
|
176
|
+
}
|
|
177
|
+
return client;
|
|
178
|
+
};
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Use `getToken({ template: "convex" })` (not the default session token). In server loaders use `process.env.VITE_CONVEX_URL` (not `import.meta.env`).
|
|
182
|
+
|
|
183
|
+
### Bootstrap — `surfaces/.../bootstrap.ts`
|
|
184
|
+
|
|
185
|
+
- Accept `{ client: ConvexHttpClient }` from the route loader
|
|
186
|
+
- Use `client.query(api...)` only — no `ConvexReactClient`, `convexQuery`, or `ensureQueryData` in bootstrap
|
|
187
|
+
- May `throw redirect(...)` for invalid params
|
|
188
|
+
|
|
189
|
+
### Route module
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
export async function loader(args: Route.LoaderArgs) {
|
|
193
|
+
const client = await createConvexClient(args);
|
|
194
|
+
return bootstrapAllTodos({ client });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function clientLoader({ serverLoader }: Route.ClientLoaderArgs) {
|
|
198
|
+
return serverLoader();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export default function Page({ loaderData: bootstrap }: Route.ComponentProps) {
|
|
202
|
+
return <AllTodos bootstrap={bootstrap} />;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Client `create.tsx`
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const { data: todos } = useQuery({
|
|
210
|
+
...convexQuery(api.todos.crud.listTodos, {}),
|
|
211
|
+
initialData: bootstrap.todos as never,
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Spread `convexQuery` first, then `initialData`. The `as never` cast is often needed for serialized vs local types.
|
|
216
|
+
|
|
217
|
+
### Anti-patterns
|
|
218
|
+
|
|
219
|
+
| Wrong | Right |
|
|
220
|
+
|-------|--------|
|
|
221
|
+
| `clientLoader` calling `ensureQueryData(convexQuery(...))` for SSR | Server `loader` + `clientLoader` → `serverLoader()` |
|
|
222
|
+
| Building `ConvexHttpClient` inside bootstrap | Receive `client` from `createConvexClient` |
|
|
223
|
+
| Duplicating Clerk redirects in every bootstrap | Centralize in `createConvexClient` / middleware |
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Shell vs pages
|
|
228
|
+
|
|
229
|
+
- **Layout-mounted** (e.g. sidebar): `useEffect(() => installSidebar(...), [])` once.
|
|
230
|
+
- **Route-mounted** pages: synchronous **`useRef` install guard** on first render (avoids empty flash; StrictMode-safe).
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Surface entry (`[feature].tsx`)
|
|
235
|
+
|
|
236
|
+
- Receives **`bootstrap` from the route** — never fetch the primary payload inside the surface
|
|
237
|
+
- `useMemo(() => createLayout(), [])` once
|
|
238
|
+
- `useRef` guard calls `installFeature({ layout, bootstrap, navigate })` synchronously on first run
|
|
239
|
+
- Obtain `useNavigate` (and similar) here and **pass into `install`**, not inside `install` itself
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## `install.tsx`
|
|
244
|
+
|
|
245
|
+
- **New `Controller()` here** — exactly once; shared across all `create` closures
|
|
246
|
+
- Use dynamic `import()` per section for code splitting
|
|
247
|
+
- Plain function — not a hook, not a component
|
|
248
|
+
- Do not use React Context to replace explicit controller passing
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## `layout.tsx`
|
|
253
|
+
|
|
254
|
+
- `LayoutController` holds `@observable.ref` slots for `ComponentType`
|
|
255
|
+
- `@action` setters for slots; observer shell renders skeletons until slots load
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Controllers and presenters
|
|
260
|
+
|
|
261
|
+
- **Controller**: cross-section UI state; `convex.mutation` / `convex.query` when needed; **no React**
|
|
262
|
+
- **Presenter**: one section’s logic; same rules — **no React**
|
|
263
|
+
- Prefer **`@action async`** without extra `runInAction` for Stage 3 decorators when using MobX’s supported pattern
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## `create.tsx`
|
|
268
|
+
|
|
269
|
+
- Call child factories **outside** the returned `observer()` so identities stay stable
|
|
270
|
+
- Inside `observer`: `useQuery`, `useForm`, etc.
|
|
271
|
+
- Pass **`initialData: bootstrap.\*`** for the same Convex function used in SSR
|
|
272
|
+
- No `useMutation` for routine writes — use `convex.mutation` in controller/presenter and pass callbacks
|
|
273
|
+
- No `useState` for feature UI state — controller/presenter own that
|
|
274
|
+
- Inline JSX handlers: **block or multi-line** — not one-liner `onClick={() => foo()}`
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Display components
|
|
279
|
+
|
|
280
|
+
- Props + callbacks only; **no** `Doc<"todos">` / raw Convex types — import **`Todo`**, **`TodoId`** from `@/api/todos/types` (or the entity’s `types.ts`)
|
|
281
|
+
- Never import **`Ent<...>`** on the frontend
|
|
282
|
+
- **Forms**: `useForm` + `<FormProvider>` in `create.tsx`; display uses **`useFormContext()`** — never pass `UseFormReturn` as a prop
|
|
283
|
+
- **Thin UIs** can live inline in `create.tsx` without a separate display file
|
|
284
|
+
- Arrow functions, named `Props` type, explicit `return (...)` for JSX
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Schema (`schema.ts`)
|
|
289
|
+
|
|
290
|
+
Same PascalCase name for type and Zod value (see `coding_standards.mdc`):
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
export type NewTodoSchema = z.infer<typeof NewTodoSchema>;
|
|
294
|
+
export const NewTodoSchema = z.object({
|
|
295
|
+
title: z.string().min(1, "Title is required"),
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Factory opts vs observer props
|
|
302
|
+
|
|
303
|
+
- **Opts** — known when the factory runs (`controller`, `bootstrap`, child components created by parent). Closed over.
|
|
304
|
+
- **Props** on the observer — per-render / per-item values (`todoId`, list row props).
|
|
305
|
+
|
|
306
|
+
Component slots are **`ComponentType`**, not `ReactNode`.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## DI tree
|
|
311
|
+
|
|
312
|
+
Nested surfaces (sheets, dialogs) live **under** the section that constructs them so dependencies stay a subtree of that section’s `create.tsx`.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Next.js variant
|
|
317
|
+
|
|
318
|
+
Adapt loader and client-entry patterns to your framework’s routing and data APIs — layer names and surface rules stay the same.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Quick reference
|
|
323
|
+
|
|
324
|
+
| File | Hooks? | Controller |
|
|
325
|
+
|------|--------|------------|
|
|
326
|
+
| Route module | No in loader | No |
|
|
327
|
+
| `bootstrap.ts` | No | No |
|
|
328
|
+
| `[feature].tsx` | `useRef`, `useMemo`, router | No — passes to install |
|
|
329
|
+
| `install.tsx` | No | **Instantiates** |
|
|
330
|
+
| `layout.tsx` | No | No |
|
|
331
|
+
| `*_controller.ts` | No (class) | — |
|
|
332
|
+
| `*_presenter.ts` | No (class) | — |
|
|
333
|
+
| `create.tsx` | Yes, inside observer | Receives via closure |
|
|
334
|
+
| Display | `useFormContext` only | No |
|
|
@@ -11,19 +11,30 @@ permissions:
|
|
|
11
11
|
contents: read
|
|
12
12
|
|
|
13
13
|
jobs:
|
|
14
|
-
|
|
15
|
-
name:
|
|
14
|
+
build:
|
|
15
|
+
name: Build & Type-check
|
|
16
16
|
runs-on: ubuntu-latest
|
|
17
|
+
env:
|
|
18
|
+
SKIP_HOOKS: "1"
|
|
19
|
+
|
|
17
20
|
steps:
|
|
18
21
|
- uses: actions/checkout@v4
|
|
19
22
|
with:
|
|
20
23
|
fetch-depth: 0
|
|
21
24
|
|
|
22
|
-
- uses: oven-sh/setup-bun@
|
|
25
|
+
- uses: oven-sh/setup-bun@v2
|
|
23
26
|
with:
|
|
24
27
|
bun-version: latest
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: bun install --frozen-lockfile
|
|
31
|
+
|
|
32
|
+
- name: Typecheck affected projects
|
|
33
|
+
run: bun run typecheck
|
|
34
|
+
|
|
35
|
+
- uses: nrwl/nx-set-shas@v4
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
- name: Build affected projects
|
|
38
|
+
run: bun nx affected -t build
|
|
39
|
+
# - name: Test affected projects
|
|
40
|
+
# run: bun nx affected -t test
|
|
@@ -50,12 +50,16 @@ type RouteFiles = {
|
|
|
50
50
|
id: "routes/(dashboard)/index";
|
|
51
51
|
page: "/";
|
|
52
52
|
};
|
|
53
|
-
"./routes/(dashboard)/todos/
|
|
54
|
-
id: "routes/(dashboard)/todos/
|
|
53
|
+
"./routes/(dashboard)/(todos)/layout.tsx": {
|
|
54
|
+
id: "routes/(dashboard)/(todos)/layout";
|
|
55
|
+
page: "/todos" | "/todos/:id";
|
|
56
|
+
};
|
|
57
|
+
"./routes/(dashboard)/(todos)/index.tsx": {
|
|
58
|
+
id: "routes/(dashboard)/(todos)/index";
|
|
55
59
|
page: "/todos";
|
|
56
60
|
};
|
|
57
|
-
"./routes/(dashboard)/todos/[id].tsx": {
|
|
58
|
-
id: "routes/(dashboard)/todos/[id]";
|
|
61
|
+
"./routes/(dashboard)/(todos)/[id].tsx": {
|
|
62
|
+
id: "routes/(dashboard)/(todos)/[id]";
|
|
59
63
|
page: "/todos/:id";
|
|
60
64
|
};
|
|
61
65
|
};
|
|
@@ -66,6 +70,7 @@ type RouteModules = {
|
|
|
66
70
|
"routes/(auth)/sign-in/index": typeof import("./src/./routes/(auth)/sign-in/index.tsx");
|
|
67
71
|
"routes/(dashboard)/layout": typeof import("./src/./routes/(dashboard)/layout.tsx");
|
|
68
72
|
"routes/(dashboard)/index": typeof import("./src/./routes/(dashboard)/index.tsx");
|
|
69
|
-
"routes/(dashboard)/todos/
|
|
70
|
-
"routes/(dashboard)/todos/
|
|
73
|
+
"routes/(dashboard)/(todos)/layout": typeof import("./src/./routes/(dashboard)/(todos)/layout.tsx");
|
|
74
|
+
"routes/(dashboard)/(todos)/index": typeof import("./src/./routes/(dashboard)/(todos)/index.tsx");
|
|
75
|
+
"routes/(dashboard)/(todos)/[id]": typeof import("./src/./routes/(dashboard)/(todos)/[id].tsx");
|
|
71
76
|
};
|
|
@@ -5,7 +5,7 @@ import type { GetInfo, GetAnnotations } from "react-router/internal";
|
|
|
5
5
|
type Module = typeof import("../[id].js")
|
|
6
6
|
|
|
7
7
|
type Info = GetInfo<{
|
|
8
|
-
file: "./routes/(dashboard)/todos/[id].tsx",
|
|
8
|
+
file: "./routes/(dashboard)/(todos)/[id].tsx",
|
|
9
9
|
module: Module
|
|
10
10
|
}>
|
|
11
11
|
|
|
@@ -16,7 +16,10 @@ type Matches = [{
|
|
|
16
16
|
id: "routes/(dashboard)/layout";
|
|
17
17
|
module: typeof import("../../layout.js");
|
|
18
18
|
}, {
|
|
19
|
-
id: "routes/(dashboard)/todos/
|
|
19
|
+
id: "routes/(dashboard)/(todos)/layout";
|
|
20
|
+
module: typeof import("../layout.js");
|
|
21
|
+
}, {
|
|
22
|
+
id: "routes/(dashboard)/(todos)/[id]";
|
|
20
23
|
module: typeof import("../[id].js");
|
|
21
24
|
}];
|
|
22
25
|
|
|
@@ -5,7 +5,7 @@ import type { GetInfo, GetAnnotations } from "react-router/internal";
|
|
|
5
5
|
type Module = typeof import("../index.js")
|
|
6
6
|
|
|
7
7
|
type Info = GetInfo<{
|
|
8
|
-
file: "./routes/(dashboard)/todos/index.tsx",
|
|
8
|
+
file: "./routes/(dashboard)/(todos)/index.tsx",
|
|
9
9
|
module: Module
|
|
10
10
|
}>
|
|
11
11
|
|
|
@@ -16,7 +16,10 @@ type Matches = [{
|
|
|
16
16
|
id: "routes/(dashboard)/layout";
|
|
17
17
|
module: typeof import("../../layout.js");
|
|
18
18
|
}, {
|
|
19
|
-
id: "routes/(dashboard)/todos/
|
|
19
|
+
id: "routes/(dashboard)/(todos)/layout";
|
|
20
|
+
module: typeof import("../layout.js");
|
|
21
|
+
}, {
|
|
22
|
+
id: "routes/(dashboard)/(todos)/index";
|
|
20
23
|
module: typeof import("../index.js");
|
|
21
24
|
}];
|
|
22
25
|
|
package/templates/apps/web/.react-router/types/src/routes/(dashboard)/(todos)/+types/layout.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Generated by React Router
|
|
2
|
+
|
|
3
|
+
import type { GetInfo, GetAnnotations } from "react-router/internal";
|
|
4
|
+
|
|
5
|
+
type Module = typeof import("../layout.js")
|
|
6
|
+
|
|
7
|
+
type Info = GetInfo<{
|
|
8
|
+
file: "./routes/(dashboard)/(todos)/layout.tsx",
|
|
9
|
+
module: Module
|
|
10
|
+
}>
|
|
11
|
+
|
|
12
|
+
type Matches = [{
|
|
13
|
+
id: "root";
|
|
14
|
+
module: typeof import("../../../../root.js");
|
|
15
|
+
}, {
|
|
16
|
+
id: "routes/(dashboard)/layout";
|
|
17
|
+
module: typeof import("../../layout.js");
|
|
18
|
+
}, {
|
|
19
|
+
id: "routes/(dashboard)/(todos)/layout";
|
|
20
|
+
module: typeof import("../layout.js");
|
|
21
|
+
}];
|
|
22
|
+
|
|
23
|
+
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, false>;
|
|
24
|
+
|
|
25
|
+
export namespace Route {
|
|
26
|
+
// links
|
|
27
|
+
export type LinkDescriptors = Annotations["LinkDescriptors"];
|
|
28
|
+
export type LinksFunction = Annotations["LinksFunction"];
|
|
29
|
+
|
|
30
|
+
// meta
|
|
31
|
+
export type MetaArgs = Annotations["MetaArgs"];
|
|
32
|
+
export type MetaDescriptors = Annotations["MetaDescriptors"];
|
|
33
|
+
export type MetaFunction = Annotations["MetaFunction"];
|
|
34
|
+
|
|
35
|
+
// headers
|
|
36
|
+
export type HeadersArgs = Annotations["HeadersArgs"];
|
|
37
|
+
export type HeadersFunction = Annotations["HeadersFunction"];
|
|
38
|
+
|
|
39
|
+
// middleware
|
|
40
|
+
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
|
|
41
|
+
|
|
42
|
+
// clientMiddleware
|
|
43
|
+
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
|
|
44
|
+
|
|
45
|
+
// loader
|
|
46
|
+
export type LoaderArgs = Annotations["LoaderArgs"];
|
|
47
|
+
|
|
48
|
+
// clientLoader
|
|
49
|
+
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
|
|
50
|
+
|
|
51
|
+
// action
|
|
52
|
+
export type ActionArgs = Annotations["ActionArgs"];
|
|
53
|
+
|
|
54
|
+
// clientAction
|
|
55
|
+
export type ClientActionArgs = Annotations["ClientActionArgs"];
|
|
56
|
+
|
|
57
|
+
// HydrateFallback
|
|
58
|
+
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
|
|
59
|
+
|
|
60
|
+
// Component
|
|
61
|
+
export type ComponentProps = Annotations["ComponentProps"];
|
|
62
|
+
|
|
63
|
+
// ErrorBoundary
|
|
64
|
+
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
|
|
65
|
+
}
|
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "web",
|
|
3
3
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"sourceRoot": "apps/
|
|
4
|
+
"sourceRoot": "apps/web",
|
|
5
5
|
"projectType": "application",
|
|
6
6
|
"tags": [],
|
|
7
7
|
"targets": {
|
|
8
|
+
"typecheck": {
|
|
9
|
+
"executor": "nx:run-commands",
|
|
10
|
+
"options": {
|
|
11
|
+
"cwd": "apps/web",
|
|
12
|
+
"command": "bunx react-router typecheck"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
8
15
|
"dev": {
|
|
9
16
|
"executor": "nx:run-commands",
|
|
10
17
|
"options": {
|
|
11
|
-
"cwd": "apps/
|
|
18
|
+
"cwd": "apps/web",
|
|
12
19
|
"command": "bunx react-router dev --port 3000"
|
|
13
20
|
}
|
|
14
21
|
},
|
|
15
22
|
"build": {
|
|
16
23
|
"executor": "nx:run-commands",
|
|
17
24
|
"options": {
|
|
18
|
-
"cwd": "apps/
|
|
25
|
+
"cwd": "apps/web",
|
|
19
26
|
"command": "bunx react-router build"
|
|
20
27
|
}
|
|
21
28
|
}
|