create-aron-app 0.1.6 → 0.1.7
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/dist/index.js +50 -115
- package/package.json +5 -2
- package/templates/{_base/.cursor → .cursor}/rules/api_architecture.mdc +27 -33
- package/templates/{_base/.cursor → .cursor}/rules/coding_standards.mdc +1 -1
- package/templates/{_base/.cursor → .cursor}/rules/convex_rules.mdc +78 -422
- package/templates/.cursor/rules/frontend_architecture_core.mdc +495 -0
- package/templates/.cursor/rules/frontend_architecture_nextjs.mdc +458 -0
- package/templates/.cursor/rules/frontend_architecture_reactrouter.mdc +473 -0
- package/templates/apps/api/_generated/api.d.ts +57 -0
- package/templates/apps/api/_generated/api.js +23 -0
- package/templates/apps/api/_generated/dataModel.d.ts +60 -0
- package/templates/apps/api/_generated/server.d.ts +143 -0
- package/templates/apps/api/_generated/server.js +93 -0
- package/templates/apps/api/http.ts +16 -0
- package/templates/apps/nextjs/.env.example +10 -0
- package/templates/{nextjs → apps/nextjs}/project.json +5 -5
- package/templates/{nextjs → apps/nextjs}/src/app/(auth)/not-allowed/page.tsx +1 -0
- package/templates/apps/nextjs/src/app/(dashboard)/layout.tsx +22 -0
- package/templates/apps/nextjs/src/app/(dashboard)/page.tsx +12 -0
- package/templates/{nextjs → apps/nextjs}/src/app/(dashboard)/todos/[id]/page.tsx +5 -2
- package/templates/apps/nextjs/src/app/(dashboard)/todos/page.tsx +19 -0
- package/templates/apps/nextjs/src/middleware.ts +18 -0
- package/templates/apps/nextjs/src/surfaces/home/bootstrap.ts +9 -0
- package/templates/apps/nextjs/src/surfaces/home/home.tsx +27 -0
- package/templates/apps/nextjs/src/surfaces/home/install.tsx +17 -0
- package/templates/apps/nextjs/src/surfaces/home/layout.tsx +44 -0
- package/templates/apps/nextjs/src/surfaces/home/main/create.tsx +34 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/install.tsx +23 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/layout.tsx +118 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/create.tsx +19 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_config.ts +22 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_main.tsx +25 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +33 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/sidebar.tsx +23 -0
- package/templates/{nextjs/src/ui/sidebar/nav_link.tsx → apps/nextjs/src/surfaces/sidebar/ui/sidebar_nav_link.tsx} +13 -10
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/create.tsx +28 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/user_menu.tsx +42 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos.tsx +29 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos_controller.ts +61 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/header/create.tsx +23 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/install.tsx +23 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/layout.tsx +44 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/create.tsx +49 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/main.tsx +70 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +56 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +99 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/bootstrap.ts +32 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/create.tsx +26 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/header.tsx +22 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/install.tsx +27 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/layout.tsx +55 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/create.tsx +38 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/main.tsx +49 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo.tsx +29 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo_controller.ts +13 -0
- package/templates/apps/nextjs/src/utils/auth.ts +18 -0
- package/templates/apps/react-router/.env.example +10 -0
- package/templates/apps/react-router/.react-router/types/+future.ts +9 -0
- package/templates/apps/react-router/.react-router/types/+routes.ts +71 -0
- package/templates/apps/react-router/.react-router/types/+server-build.d.ts +18 -0
- package/templates/apps/react-router/.react-router/types/src/+types/root.ts +59 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(auth)/+types/layout.ts +62 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +65 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/index.ts +65 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/layout.ts +62 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/[id].ts +65 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/index.ts +65 -0
- package/templates/{react-router → apps/react-router}/project.json +4 -4
- package/templates/apps/react-router/src/app.css +3 -0
- package/templates/{react-router → apps/react-router}/src/components/error_boundary.tsx +1 -1
- package/templates/{react-router → apps/react-router}/src/providers/api_auth_provider.tsx +2 -0
- package/templates/{react-router/src/routes/auth → apps/react-router/src/routes/(auth)}/layout.tsx +1 -1
- package/templates/apps/react-router/src/routes/(dashboard)/index.tsx +19 -0
- package/templates/apps/react-router/src/routes/(dashboard)/layout.tsx +37 -0
- package/templates/apps/react-router/src/routes/(dashboard)/todos/[id].tsx +19 -0
- package/templates/apps/react-router/src/routes/(dashboard)/todos/index.tsx +19 -0
- package/templates/apps/react-router/src/routes.ts +12 -0
- package/templates/apps/react-router/src/surfaces/home/bootstrap.ts +9 -0
- package/templates/apps/react-router/src/surfaces/home/home.tsx +25 -0
- package/templates/apps/react-router/src/surfaces/home/install.tsx +17 -0
- package/templates/apps/react-router/src/surfaces/home/layout.tsx +35 -0
- package/templates/apps/react-router/src/surfaces/home/main/create.tsx +32 -0
- package/templates/apps/react-router/src/surfaces/sidebar/install.tsx +23 -0
- package/templates/apps/react-router/src/surfaces/sidebar/layout.tsx +110 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_main/create.tsx +31 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_main/nav_main.tsx +42 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +31 -0
- package/templates/apps/react-router/src/surfaces/sidebar/sidebar.tsx +18 -0
- package/templates/apps/react-router/src/surfaces/sidebar/user_menu/create.tsx +26 -0
- package/templates/{react-router/src/layouts/sidebar/sidebar_aside → apps/react-router/src/surfaces/sidebar/user_menu}/user_menu.tsx +13 -9
- package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos.tsx +25 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos_controller.ts +47 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/bootstrap.ts +18 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/header/create.tsx +21 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/install.tsx +20 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/layout.tsx +35 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/create.tsx +47 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/main.tsx +68 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +54 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +97 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/bootstrap.ts +36 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/header/create.tsx +32 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/header/header.tsx +25 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/install.tsx +27 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/layout.tsx +45 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/main/create.tsx +35 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/main/main.tsx +47 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo.tsx +27 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo_controller.ts +16 -0
- package/templates/{react-router → apps/react-router}/vite.config.ts +27 -3
- package/templates/{_base/biome.json → biome.json} +7 -0
- package/templates/bun.lock +3187 -0
- package/templates/{_base/emails → emails}/project.json +1 -1
- package/templates/{_base/nx.json → nx.json} +11 -0
- package/templates/package.json +92 -0
- package/templates/{_base/tsconfig.base.json → tsconfig.base.json} +4 -1
- package/templates/_base/.cursor/rules/frontend_rules.mdc +0 -268
- package/templates/_base/.env.convex.example +0 -3
- package/templates/_base/_gitignore +0 -58
- package/templates/_base/package.json +0 -73
- package/templates/nextjs/.env.example +0 -8
- package/templates/nextjs/src/app/(dashboard)/layout.tsx +0 -27
- package/templates/nextjs/src/app/(dashboard)/page.tsx +0 -5
- package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +0 -16
- package/templates/nextjs/src/middleware.ts +0 -18
- package/templates/nextjs/src/surfaces/home_surface.tsx +0 -22
- package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +0 -97
- package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +0 -107
- package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +0 -90
- package/templates/nextjs/src/ui/sidebar/sidebar.tsx +0 -125
- package/templates/react-router/.env.example +0 -8
- package/templates/react-router/src/app.css +0 -3
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +0 -76
- package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +0 -22
- package/templates/react-router/src/routes/index.tsx +0 -9
- package/templates/react-router/src/routes/layout.tsx +0 -26
- package/templates/react-router/src/routes/todos/[id].tsx +0 -22
- package/templates/react-router/src/routes/todos/index.tsx +0 -13
- package/templates/react-router/src/routes.ts +0 -12
- package/templates/react-router/src/surfaces/home_surface.tsx +0 -20
- package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +0 -87
- package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +0 -102
- package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +0 -81
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-webhooks/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/agents/openai.yml +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn-small.png +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn.png +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/cli.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/customization.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/evals/evals.json +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/mcp.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/base-vs-radix.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/composition.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/forms.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/icons.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/styling.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/commands/builder.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/commands/pr.md +0 -0
- /package/templates/{_base/.github → .github}/workflows/ci.yml +0 -0
- /package/templates/{_base/.nvmrc → .nvmrc} +0 -0
- /package/templates/{_base/.vscode → .vscode}/settings.json +0 -0
- /package/templates/{_base/apps → apps}/api/auth.config.ts +0 -0
- /package/templates/{_base/apps → apps}/api/functions.ts +0 -0
- /package/templates/{_base/apps → apps}/api/project.json +0 -0
- /package/templates/{_base/apps → apps}/api/schema.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/crud.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/schema.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/types.ts +0 -0
- /package/templates/{_base/apps → apps}/api/tsconfig.json +0 -0
- /package/templates/{_base/apps → apps}/api/types.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/index.d.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/next-env.d.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/next.config.js +0 -0
- /package/templates/{nextjs → apps/nextjs}/postcss.config.js +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/layout.tsx +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/app.css +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/layout.tsx +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/providers/convex_provider.tsx +0 -0
- /package/templates/{nextjs/src → apps/nextjs/src/utils}/convex.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/utils/font.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/tsconfig.json +0 -0
- /package/templates/{react-router → apps/react-router}/postcss.config.js +0 -0
- /package/templates/{react-router → apps/react-router}/public/favicon.ico +0 -0
- /package/templates/{react-router → apps/react-router}/react-router.config.ts +0 -0
- /package/templates/{react-router → apps/react-router}/src/root.tsx +0 -0
- /package/templates/{react-router/src/routes/auth/sign-in.tsx → apps/react-router/src/routes/(auth)/sign-in/index.tsx} +0 -0
- /package/templates/{react-router → apps/react-router}/tsconfig.json +0 -0
- /package/templates/{_base/convex.json → convex.json} +0 -0
- /package/templates/{_base/emails → emails}/tsconfig.json +0 -0
- /package/templates/{_base/emails → emails}/welcome_email.tsx +0 -0
- /package/templates/{_base/scripts → scripts}/sync_convex_env.ts +0 -0
- /package/templates/{_base/shared → shared}/assets/image.d.ts +0 -0
- /package/templates/{_base/shared → shared}/assets/src/styles/global.css +0 -0
- /package/templates/{_base/shared → shared}/assets/tsconfig.json +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/alert_dialog.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/badge.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/basic_data_table.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/button.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/button_group.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/card.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/checkbox.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/command.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/dialog.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/dropdown_menu.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/form.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/input.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/label.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/popover.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/radio_group.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/resizable.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/scroll_area.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/select.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/separator.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/sheet.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/side_bar.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/skeleton.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/spinner.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/switch.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/table.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/text_area.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/tooltip.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/utils.ts +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_press.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_release.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mobile.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_click.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_location.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_outside_click.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_query_params.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/tsconfig.json +0 -0
- /package/templates/{_base/shared → shared}/utils/src/convex.ts +0 -0
- /package/templates/{_base/shared → shared}/utils/src/time.ts +0 -0
- /package/templates/{_base/shared → shared}/utils/tsconfig.json +0 -0
- /package/templates/{_base/skills-lock.json → skills-lock.json} +0 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Core frontend surface architecture — framework-agnostic layer model, MobX patterns, dependency injection, data flow, and component rules. Apply across all frontend apps.
|
|
3
|
+
globs: apps/*/src/**
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Frontend Surface Architecture — Core
|
|
8
|
+
|
|
9
|
+
Each surface is an isolated vertical slice. Data flows strictly downward. State is held in observable MobX classes, not component hooks. Components are assembled lazily via dynamic `import()`.
|
|
10
|
+
|
|
11
|
+
**Key principle: display components are dumb, creates are smart closures, controllers are the brain.**
|
|
12
|
+
|
|
13
|
+
**When to use a Presenter vs Controller:** Use a **presenter** when a section has its own business logic (mutations, input state, submit handling) that doesn't need to coordinate with other sections. Use a **controller** when state must be shared across multiple sections (e.g. dialog open/close, selected item). Presenters are scoped to a single component; controllers span the feature.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Layer Stack
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
[route entry] → mounts surface, passes bootstrap
|
|
21
|
+
bootstrap.ts → param validation + data loading (no UI)
|
|
22
|
+
[feature].tsx → surface entry (useRef guard, triggers install, receives bootstrap)
|
|
23
|
+
install.tsx → wiring (lazy-imports creates, seeds layout slots, instantiates controller)
|
|
24
|
+
layout.tsx → LayoutController + reactive Layout shell
|
|
25
|
+
[feature]_controller.ts → MobX class managing cross-component state
|
|
26
|
+
[section]_presenter.ts → MobX class scoped to one section (business logic, mutations, input state)
|
|
27
|
+
[section]/create.tsx → factory returning an observer() component closed over deps
|
|
28
|
+
[section]/[section].tsx → pure display component (props only, no hooks — see useForm exception)
|
|
29
|
+
ui/ → shared display components scoped to this surface
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Folder Structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
surfaces/
|
|
38
|
+
todo_detail/
|
|
39
|
+
todo_detail.tsx # surface entry component (receives bootstrap)
|
|
40
|
+
bootstrap.ts # data loading, exports TodoDetailBootstrap
|
|
41
|
+
install.tsx # wiring — lazy imports, seeds layout, instantiates controller
|
|
42
|
+
layout.tsx # LayoutController + createLayout()
|
|
43
|
+
todo_detail_controller.ts # MobX state class
|
|
44
|
+
header/
|
|
45
|
+
create.tsx # createHeader({ navigate }) — thin component, no display file
|
|
46
|
+
main/
|
|
47
|
+
create.tsx # createMain({ controller, bootstrap })
|
|
48
|
+
main.tsx # pure display: MainProps
|
|
49
|
+
new_todo_sheet/ # child surface inside parent section (DI tree integrity)
|
|
50
|
+
create.tsx # createNewTodoSheet({ controller })
|
|
51
|
+
new_todo_sheet.tsx # display with useFormContext (form exception)
|
|
52
|
+
schema.ts # Zod schema + inferred type
|
|
53
|
+
ui/
|
|
54
|
+
status_badge.tsx # shared display scoped to this surface
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Naming conventions:
|
|
58
|
+
- `Opts` suffix for factory/install arg types: `CreateHeaderOpts`, `InstallTodoDetailOpts`
|
|
59
|
+
- `Props` suffix for component prop types: `HeaderProps`, `MainProps`
|
|
60
|
+
- Controller class scoped to feature: `TodoDetailController`, `DashboardController`
|
|
61
|
+
- `bootstrap` stays as `bootstrap` — one per surface
|
|
62
|
+
- All file names must be **snake_case** (e.g. `todo_detail.tsx`, `new_todo_sheet.tsx`)
|
|
63
|
+
- Use **`All`** for list surfaces and **`Single`** for id-specific surfaces
|
|
64
|
+
- **CRUD naming:** Use `new`, `edit`, `delete` for frontend CRUD component names — never `create` (clashes with the `create.tsx` factory pattern). E.g., `new_todo_sheet/`, `edit_todo_dialog/`, `delete_todo_dialog/`.
|
|
65
|
+
- **Component name de-scoping:** Display components must NOT repeat parent surface/section names. The directory path provides scope. E.g., inside `single_todo/header/`, the display is `header.tsx` exporting `Header` — not `single_todo_header.tsx` exporting `SingleTodoHeader`. Inside `all_todos/main/`, the display is `main.tsx` exporting `Main`.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Bootstrap Pattern
|
|
70
|
+
|
|
71
|
+
Bootstrap validates params, handles auth redirects, and loads the primary entity for the surface. Bootstrap data is immutable after mount — mutable state lives in the controller.
|
|
72
|
+
|
|
73
|
+
Rules:
|
|
74
|
+
- One bootstrap per surface; lives in `surfaces/[feature]/bootstrap.ts`
|
|
75
|
+
- Auth failures/redirects belong here, not in components
|
|
76
|
+
- Export `[Feature]BootstrapArgs` and `[Feature]Bootstrap` (inferred from return type)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## `[feature].tsx` — Surface Entry Component
|
|
81
|
+
|
|
82
|
+
Receives `bootstrap` from the route. Creates the layout shell, triggers install once on mount via a `useRef` guard. The `useRef` guard runs install synchronously during the first render — StrictMode safe, no double-fire.
|
|
83
|
+
|
|
84
|
+
**Dependency injection:** Libraries and framework hooks (e.g. router, toast) must be declared at the surface level and injected downwards into install → creates → controllers/presenters. Never call router hooks inside install or create — obtain them in the surface entry and pass them into install.
|
|
85
|
+
|
|
86
|
+
Rules:
|
|
87
|
+
- Bootstrap is passed in as a prop from the route — never fetched inside the surface
|
|
88
|
+
- `createLayout()` inside `useMemo` runs once synchronously
|
|
89
|
+
- `useRef` guard runs install synchronously on first render, never again
|
|
90
|
+
- No business logic here — mounting point only
|
|
91
|
+
- **Never instantiate controllers here** — controller creation belongs exclusively in `install.tsx`
|
|
92
|
+
- External deps (router, toast, etc.) are obtained at surface level and injected into install
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## `install.tsx` — Wiring Function
|
|
97
|
+
|
|
98
|
+
Single place where all deps are assembled. **Instantiates the controller**, lazy-loads creates. This is the **explicit dependency manifest** for the surface.
|
|
99
|
+
|
|
100
|
+
Do not use React Context to share the controller. Every component's access to the controller is explicit and traceable through the install chain.
|
|
101
|
+
|
|
102
|
+
Rules:
|
|
103
|
+
- **`controller` instantiated here** — exactly once, shared across all creates
|
|
104
|
+
- Dynamic `import()` makes each section a separate bundle chunk
|
|
105
|
+
- `install` is a plain function, not a component or hook
|
|
106
|
+
- Child creates that don't need the controller should not receive it — access is explicit
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## `layout.tsx` — Layout Controller and Shell
|
|
111
|
+
|
|
112
|
+
Rules:
|
|
113
|
+
- Use `observable.ref` for component slots — MobX only needs reference equality
|
|
114
|
+
- Template structure (flex, grid, spacing) defined here; sections don't know about neighbours
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## `[feature]_controller.ts` — Controller
|
|
119
|
+
|
|
120
|
+
Rules:
|
|
121
|
+
- No React imports — pure class
|
|
122
|
+
- Use `computed get` for derived values — components re-render only when the value changes
|
|
123
|
+
- Controllers manage coordination state (what is open, what is selected)
|
|
124
|
+
- **UI state** (dialog open/close, sheet visibility) belongs in the controller, not in `useState` calls inside `create.tsx`
|
|
125
|
+
- Server/async data belongs in the `create` layer, not the controller
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## `[section]_presenter.ts` — Presenter (component-scoped)
|
|
130
|
+
|
|
131
|
+
Use when a section has its own business logic (mutations, input state, submit handling) that doesn't need to coordinate with other sections. Calls Convex mutations directly via the exported `convex` singleton — no hooks required.
|
|
132
|
+
|
|
133
|
+
Rules:
|
|
134
|
+
- No React imports — pure class
|
|
135
|
+
- Call mutations via `convex.mutation(api.xxx, args)` — convex singleton imported from the provider
|
|
136
|
+
- Options injected via constructor — use `Opts` suffix, destructure and assign to private readonly fields
|
|
137
|
+
- `create` keeps only hooks that cannot be moved (e.g. `useFormContext` which requires React context)
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## `[section]/create.tsx` — Create Function
|
|
142
|
+
|
|
143
|
+
Factory closing over `controller` and `bootstrap`, returning an `observer()` component.
|
|
144
|
+
|
|
145
|
+
Rules:
|
|
146
|
+
- Child `create` calls go at the top of the factory body, **outside** the returned observer — run once, not per render
|
|
147
|
+
- Returned component is always wrapped in `observer()`
|
|
148
|
+
- Hook calls (`useQuery`) go **inside** the returned observer
|
|
149
|
+
- Pass `bootstrap` data as `initialData` to seed the query on first render — eliminates loading flash, the Convex subscription takes over reactivity after mount
|
|
150
|
+
- No `useMutation` in creates — mutations belong in controllers (`convex.mutation()`) or presenters; pass action callbacks as props to display components
|
|
151
|
+
- **No React state** (`useState`, `useReducer`) in create.tsx — UI state (open/close, selections) belongs in a controller or presenter. Only third-party hooks (`useQuery`, `useForm`) are acceptable inside the observer.
|
|
152
|
+
- Inline JSX callbacks must be multi-line, never one-liners
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Display Components
|
|
157
|
+
|
|
158
|
+
Pure presentational. Everything via props. No hooks, no business logic, no controller/bootstrap imports.
|
|
159
|
+
|
|
160
|
+
**Exception — form display components:** Display components that own a form may call `useFormContext()` to access the form provided by the parent `create.tsx` via `<FormProvider>`. Never pass `UseFormReturn` as a prop — always use `FormProvider` + `useFormContext()`.
|
|
161
|
+
|
|
162
|
+
**Thin component rule:** If the display component is thin (few props, minimal JSX, no complex layout), skip the separate display file. The `create.tsx` alone is sufficient — it directly renders the JSX inside the observer. This avoids unnecessary files for trivial components like a back-button header.
|
|
163
|
+
|
|
164
|
+
Rules:
|
|
165
|
+
- Callbacks are plain `() => void` props — the `create.tsx` sibling maps controller actions to these props
|
|
166
|
+
- Always use domain type aliases (`Todo`, `TodoId`) — never raw `Doc<"todos">` or `Id<"todos">`
|
|
167
|
+
- Never import `Ent<T>` — it is a backend-only type from `convex-ents` carrying write methods
|
|
168
|
+
- Explicit `return (...)` always — no implicit arrow returns
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Schema Pattern
|
|
173
|
+
|
|
174
|
+
Zod schemas live in `schema.ts` inside the section directory. Type infers are declared **above** the schema object and the type name matches the schema variable name.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
export type NewTodoSchema = z.infer<typeof newTodoSchema>;
|
|
178
|
+
export const newTodoSchema = z.object({
|
|
179
|
+
title: z.string().min(1, "Title is required"),
|
|
180
|
+
description: z.string().optional(),
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Rules:
|
|
185
|
+
- `z.infer<typeof schema>` type alias goes **above** the const — hoisting makes this safe
|
|
186
|
+
- Type name matches schema name: `newTodoSchema` → `NewTodoSchema`
|
|
187
|
+
- One schema per file — no duplicate definitions
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Form Management
|
|
192
|
+
|
|
193
|
+
- Schema defined in a `schema.ts` file inside the form's section directory (e.g. `new_todo_sheet/schema.ts`)
|
|
194
|
+
- `useForm` called inside the **create.tsx** factory (inside the observer)
|
|
195
|
+
- `<FormProvider>` wraps the display component so children use `useFormContext()` — **never pass `UseFormReturn` as a prop**
|
|
196
|
+
- Display component calls `useFormContext()` to access the form (this is the one hook exception for display components)
|
|
197
|
+
- Form open/close state belongs in a **controller** (not `useState` inside create.tsx)
|
|
198
|
+
- `controller.closeDialog()` / `controller.closeNewTodoSheet()` called in `onSuccess` — UI transitions in one place
|
|
199
|
+
|
|
200
|
+
Sub-fields with `useFormContext`:
|
|
201
|
+
```typescript
|
|
202
|
+
export const createTitleField = () => {
|
|
203
|
+
return () => {
|
|
204
|
+
const { register, formState: { errors } } = useFormContext<NewTodoSchema>();
|
|
205
|
+
return <input {...register("title")} />;
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## DI Tree Integrity
|
|
213
|
+
|
|
214
|
+
Child surfaces (dialogs, sheets, drawers) must live **inside the parent section directory** that creates them. This keeps the dependency injection tree traceable — the parent's `create.tsx` lazy-imports the child surface, and the child's deps are a subset of the parent's.
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
main/
|
|
218
|
+
create.tsx # imports new_todo_sheet/create
|
|
219
|
+
main.tsx # display
|
|
220
|
+
new_todo_sheet/ # child surface — created from main/create.tsx
|
|
221
|
+
create.tsx
|
|
222
|
+
new_todo_sheet.tsx
|
|
223
|
+
schema.ts
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Do not place child surfaces as siblings of the parent section. If `main/create.tsx` creates the sheet, the sheet directory belongs under `main/`.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Component Rules
|
|
231
|
+
|
|
232
|
+
### Arrow Functions
|
|
233
|
+
|
|
234
|
+
Always export components as **const arrow functions**:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// Good
|
|
238
|
+
export const ComponentName = ({ prop1, prop2 }: ComponentNameProps) => {
|
|
239
|
+
return <div />;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Bad
|
|
243
|
+
export function ComponentName({ prop1, prop2 }: ComponentNameProps) {
|
|
244
|
+
return <div />;
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Explicit Returns
|
|
249
|
+
|
|
250
|
+
Always use explicit `return` statements in component functions. No implicit arrow returns for JSX.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// Good
|
|
254
|
+
export const Header = ({ title }: HeaderProps) => {
|
|
255
|
+
return <h1>{title}</h1>;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Bad — implicit arrow return
|
|
259
|
+
export const Header = ({ title }: HeaderProps) => <h1>{title}</h1>;
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Named Prop Types
|
|
263
|
+
|
|
264
|
+
Every component must have a named prop type declared immediately above it. Never inline prop types on the component signature.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Good
|
|
268
|
+
type HeaderProps = {
|
|
269
|
+
title: string;
|
|
270
|
+
onBack: () => void;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export const Header = ({ title, onBack }: HeaderProps) => {
|
|
274
|
+
return <h1>{title}</h1>;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Bad — inline type
|
|
278
|
+
export const Header = ({ title }: { title: string }) => {
|
|
279
|
+
return <h1>{title}</h1>;
|
|
280
|
+
};
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### No One-Liner Inline Handlers
|
|
284
|
+
|
|
285
|
+
Multi-line format is required for all inline JSX callbacks, even simple ones.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// Good
|
|
289
|
+
onToggle={(isCompleted) =>
|
|
290
|
+
controller.updateTodo({
|
|
291
|
+
todoId: bootstrap.todoId,
|
|
292
|
+
isCompleted,
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Also good — block body when there is more than one expression
|
|
297
|
+
onToggle={(isCompleted) => {
|
|
298
|
+
controller.updateTodo({
|
|
299
|
+
todoId: bootstrap.todoId,
|
|
300
|
+
isCompleted,
|
|
301
|
+
});
|
|
302
|
+
}}
|
|
303
|
+
|
|
304
|
+
// Bad — single line
|
|
305
|
+
onToggle={(isCompleted) => controller.updateTodo({ todoId: bootstrap.todoId, isCompleted })}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Create Factory Props vs Display Props
|
|
309
|
+
|
|
310
|
+
The `create` factory has two layers for receiving data: **factory opts** (known at creation time) and **observer props** (provided at render time). This applies to all prop types — component slots, IDs, data items, callbacks — not just components.
|
|
311
|
+
|
|
312
|
+
**Factory opts (`Opts`)** — values known when the factory is called. Closed over in the observer, not re-provided per render:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// header/create.tsx — component slot + controller known at creation time
|
|
316
|
+
type CreateHeaderOpts = {
|
|
317
|
+
NewTodoSheet: ComponentType;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
export const createHeader = ({ NewTodoSheet }: CreateHeaderOpts) => {
|
|
321
|
+
return observer(() => {
|
|
322
|
+
return (
|
|
323
|
+
<div>
|
|
324
|
+
<h1>Todos</h1>
|
|
325
|
+
<NewTodoSheet />
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// parent main/create.tsx
|
|
332
|
+
const NewTodoSheet = createNewTodoSheet({ controller });
|
|
333
|
+
const Header = createHeader({ NewTodoSheet });
|
|
334
|
+
|
|
335
|
+
return observer(() => {
|
|
336
|
+
return (
|
|
337
|
+
<main>
|
|
338
|
+
<Header />
|
|
339
|
+
<Main /* ... */ />
|
|
340
|
+
</main>
|
|
341
|
+
);
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Observer props (`Props`)** — values that vary per instance or are provided at render time by a parent. Declared as a named type on the observer returned by the factory:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// todo_card/create.tsx — todoId varies per list item
|
|
349
|
+
type CreateTodoCardOpts = {
|
|
350
|
+
controller: AllTodosController;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
type TodoCardProps = {
|
|
354
|
+
todoId: TodoId;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export const createTodoCard = ({ controller }: CreateTodoCardOpts) => {
|
|
358
|
+
return observer(({ todoId }: TodoCardProps) => {
|
|
359
|
+
const { data: todo } = useQuery(/* ... */);
|
|
360
|
+
return (
|
|
361
|
+
<TodoCard
|
|
362
|
+
todo={todo}
|
|
363
|
+
onDelete={() => {
|
|
364
|
+
void controller.deleteTodo({ todoId });
|
|
365
|
+
}}
|
|
366
|
+
/>
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// parent list/create.tsx — passes per-item data at render time
|
|
372
|
+
const TodoCard = createTodoCard({ controller });
|
|
373
|
+
|
|
374
|
+
return observer(() => {
|
|
375
|
+
return (
|
|
376
|
+
<ul>
|
|
377
|
+
{todos.map((todo) => (
|
|
378
|
+
<TodoCard key={todo._id} todoId={todo._id} />
|
|
379
|
+
))}
|
|
380
|
+
</ul>
|
|
381
|
+
);
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Component slots follow the same split — factory opt when known at creation time, observer prop when provided at render time:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
// observer prop for a component slot
|
|
389
|
+
type HeaderProps = {
|
|
390
|
+
SomeComponent: ComponentType;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
export const createHeader = ({ navigate }: CreateHeaderOpts) => {
|
|
394
|
+
return observer(({ SomeComponent }: HeaderProps) => {
|
|
395
|
+
return (
|
|
396
|
+
<div>
|
|
397
|
+
<SomeComponent />
|
|
398
|
+
<Header
|
|
399
|
+
onBack={() => {
|
|
400
|
+
navigate("/todos");
|
|
401
|
+
}}
|
|
402
|
+
/>
|
|
403
|
+
</div>
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Bad** — component slots or dynamic data on a pure display component:
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
// Bad — display component receiving pre-rendered ReactNode
|
|
413
|
+
type HeaderProps = {
|
|
414
|
+
toolSlot: React.ReactNode;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
export const Header = ({ toolSlot }: HeaderProps) => {
|
|
418
|
+
return (
|
|
419
|
+
<div>
|
|
420
|
+
<h1>Todos</h1>
|
|
421
|
+
{toolSlot}
|
|
422
|
+
</div>
|
|
423
|
+
);
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
<Header toolSlot={<NewTodoSheet />} />
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Rules:
|
|
430
|
+
- **Factory opts** for values known at creation time (controller, bootstrap, component slots created by the parent)
|
|
431
|
+
- **Observer props** for values that vary per instance or are provided at render time (IDs, per-item data, component slots passed down dynamically)
|
|
432
|
+
- Component slots are always `ComponentType`, never `ReactNode` — named after the component they receive
|
|
433
|
+
- Pure display components receive only plain data props and `() => void` callbacks — never `ComponentType` slots, IDs that drive data fetching, or any logic that belongs in `create.tsx`
|
|
434
|
+
|
|
435
|
+
### Domain Type Imports
|
|
436
|
+
|
|
437
|
+
Always import the aliased domain type from the entity's `types.ts`. Never use raw Convex generics in frontend code.
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
// Good
|
|
441
|
+
import type { Todo, TodoId } from "@/api/todos/types";
|
|
442
|
+
|
|
443
|
+
// Bad — raw Convex generics
|
|
444
|
+
import type { Doc, Id } from "@/api/_generated/dataModel";
|
|
445
|
+
const todoId: Id<"todos"> = ...;
|
|
446
|
+
const todo: Doc<"todos"> = ...;
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Each entity's `types.ts` exports `EntityId = Id<"entity">` and `Entity = Doc<"entity">`. Import from there.
|
|
450
|
+
|
|
451
|
+
`Ent<T>` is a backend-only type from `convex-ents`. Never import it in frontend files — use the `Entity` alias (e.g. `Todo`) from the domain `types.ts` instead.
|
|
452
|
+
|
|
453
|
+
### Path Alias Imports
|
|
454
|
+
|
|
455
|
+
Always use path alias imports (`@/...`). **No relative imports** — not even within the same feature folder. Every `import` statement must use the `@/` alias.
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
// Good — always alias
|
|
459
|
+
import { Button } from "@/ui/base/button";
|
|
460
|
+
import type { TodoId } from "@/api/todos/types";
|
|
461
|
+
import { createHeader } from "@/web/surfaces/todos/all_todos/header/create";
|
|
462
|
+
import { createNewTodoSheet } from "@/web/surfaces/todos/all_todos/main/new_todo_sheet/create";
|
|
463
|
+
|
|
464
|
+
// Bad — relative paths (even within same feature)
|
|
465
|
+
import { createLayout } from "./layout";
|
|
466
|
+
import type { AllTodosBootstrap } from "../bootstrap";
|
|
467
|
+
import { Main } from "./main";
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Install Timing Consistency
|
|
473
|
+
|
|
474
|
+
**Layout-mounted surfaces** (sidebar, persistent chrome): Use `useEffect` to call install once. The layout persists across navigations, so `useEffect` with a stable `layout` dependency is appropriate.
|
|
475
|
+
|
|
476
|
+
**Route-mounted surfaces** (pages): Use the synchronous `useRef` install guard. The surface mounts fresh per navigation, so synchronous install during first render avoids flicker.
|
|
477
|
+
|
|
478
|
+
Pick the correct pattern based on mount context and apply it consistently across all surfaces of the same type.
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## Quick Reference
|
|
483
|
+
|
|
484
|
+
| File | Can use hooks? | Imports controller? |
|
|
485
|
+
|---|---|---|
|
|
486
|
+
| Route entry | Framework-dependent | No |
|
|
487
|
+
| `bootstrap.ts` | No | No |
|
|
488
|
+
| `[feature].tsx` | `useRef`, `useMemo`, router hooks | No |
|
|
489
|
+
| `install.tsx` | No | Instantiates it |
|
|
490
|
+
| `layout.tsx` | No | No |
|
|
491
|
+
| `[name]_controller.ts` | No (pure class) | — |
|
|
492
|
+
| `[section]_presenter.ts` | No (pure class) | — |
|
|
493
|
+
| `[section]/create.tsx` | Yes (inside observer) | Yes (via closure) |
|
|
494
|
+
| `[section]/[name].tsx` | `useFormContext` only | No |
|
|
495
|
+
| `ui/[name].tsx` | No | No |
|