wcz-layout 8.1.0 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wcz-layout might be problematic. Click here for more details.
- package/dist/components.js +2 -2
- package/dist/components.js.map +1 -1
- package/dist/data/client.d.ts +1 -1
- package/dist/data/server.d.ts +1 -1
- package/dist/models.d.ts +4 -4
- package/dist/{peoplesoft-CFgBFvG-.d.ts → peoplesoft-kaMETchL.d.ts} +5 -5
- package/package.json +154 -154
- package/skills/client-db/SKILL.md +111 -0
- package/skills/db-schema/SKILL.md +69 -0
- package/skills/forms/SKILL.md +85 -0
- package/skills/routes/SKILL.md +127 -0
- package/skills/server/SKILL.md +114 -0
- package/skills/start/SKILL.md +46 -0
- package/skills/start/steps/01-git-setup.md +10 -0
- package/skills/start/steps/02-dependency-check.md +11 -0
- package/skills/start/steps/03-env-setup.md +16 -0
- package/skills/start/steps/04-project-name-setup.md +14 -0
- package/skills/start/steps/05-database-setup.md +37 -0
- package/skills/start/steps/06-entra-setup.md +59 -0
- package/skills/start/steps/07-vault-setup.md +56 -0
- package/skills/start/steps/08-generate-favicon.md +15 -0
- package/skills/start/steps/09-commit.md +15 -0
- package/skills/tables/SKILL.md +184 -0
- package/skills/api-routes/SKILL.md +0 -251
- package/skills/auth/SKILL.md +0 -268
- package/skills/data-grid/SKILL.md +0 -229
- package/skills/database-schema/SKILL.md +0 -182
- package/skills/dialogs-notifications/SKILL.md +0 -241
- package/skills/forms-validation/SKILL.md +0 -331
- package/skills/forms-validation/references/field-components.md +0 -212
- package/skills/layout-navigation/SKILL.md +0 -259
- package/skills/project-initialization/SKILL.md +0 -181
- package/skills/project-structure/SKILL.md +0 -157
- package/skills/tanstack-db-collections/SKILL.md +0 -270
- package/skills/template-init.md +0 -146
- package/skills/ui-pages/SKILL.md +0 -278
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: layout-navigation
|
|
3
|
-
description: >
|
|
4
|
-
Configure LayoutProvider with theme, options, and Navigation structure.
|
|
5
|
-
Shell: AppBar with env chip + NavigationRail (permanent sm+, modal xs).
|
|
6
|
-
Navigation items: page (title, icon, to/href), header, divider.
|
|
7
|
-
LayoutOptions: showShell, snackbarOrigin, publicRoutes. Dark mode via
|
|
8
|
-
theme.applyStyles("dark", ...) with colorSchemeSelector
|
|
9
|
-
data-mui-color-scheme. i18n with i18next, locale files auto-detected.
|
|
10
|
-
Activate when configuring the layout shell, navigation, or theming.
|
|
11
|
-
type: core
|
|
12
|
-
library: wcz-layout
|
|
13
|
-
library_version: "7.6.1"
|
|
14
|
-
sources:
|
|
15
|
-
- "wcz-layout:src/providers/LayoutProvider.tsx"
|
|
16
|
-
- "wcz-layout:src/components/core/Layout.tsx"
|
|
17
|
-
- "wcz-layout:src/models/Navigation.ts"
|
|
18
|
-
- "wcz-layout:src/models/LayoutOptions.ts"
|
|
19
|
-
- "wcz-layout:src/lib/theme.ts"
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
# Layout & Navigation
|
|
23
|
-
|
|
24
|
-
## Setup
|
|
25
|
-
|
|
26
|
-
The layout shell is configured in the root route via `LayoutProvider`:
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
// src/routes/__root.tsx
|
|
30
|
-
import { LayoutProvider, rootRouteHead, RouterNotFound, RouterError } from "wcz-layout";
|
|
31
|
-
import { theme } from "~/lib/theme";
|
|
32
|
-
import { navigation } from "~/navigation";
|
|
33
|
-
import type { LayoutOptions } from "wcz-layout/models";
|
|
34
|
-
|
|
35
|
-
const options: LayoutOptions = {
|
|
36
|
-
showShell: true,
|
|
37
|
-
publicRoutes: ["/login"],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const Route = createRootRouteWithContext()({
|
|
41
|
-
head: rootRouteHead,
|
|
42
|
-
component: RootComponent,
|
|
43
|
-
notFoundComponent: RouterNotFound,
|
|
44
|
-
errorComponent: RouterError,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
function RootComponent() {
|
|
48
|
-
return (
|
|
49
|
-
<LayoutProvider theme={theme} navigation={navigation} options={options}>
|
|
50
|
-
<Outlet />
|
|
51
|
-
</LayoutProvider>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Core Patterns
|
|
57
|
-
|
|
58
|
-
### Navigation structure
|
|
59
|
-
|
|
60
|
-
Navigation is an array of items with three types — discriminated by the presence of `kind`:
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
import type { Navigation } from "wcz-layout/models";
|
|
64
|
-
import HomeIcon from "@mui/icons-material/Home";
|
|
65
|
-
import ListIcon from "@mui/icons-material/List";
|
|
66
|
-
import SettingsIcon from "@mui/icons-material/Settings";
|
|
67
|
-
|
|
68
|
-
export const navigation: Navigation = [
|
|
69
|
-
{ title: "Home", icon: <HomeIcon />, to: "/" },
|
|
70
|
-
{ kind: "divider" },
|
|
71
|
-
{ kind: "header", title: "Management" },
|
|
72
|
-
{ title: "Todos", icon: <ListIcon />, to: "/todos" },
|
|
73
|
-
{ kind: "divider" },
|
|
74
|
-
{ title: "Settings", icon: <SettingsIcon />, to: "/settings" },
|
|
75
|
-
];
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Navigation item types
|
|
79
|
-
|
|
80
|
-
| Type | Shape | Required fields |
|
|
81
|
-
| --------- | --------------------------- | --------------- |
|
|
82
|
-
| Page item | `{ title, icon, to }` | `title`, `icon` |
|
|
83
|
-
| Divider | `{ kind: "divider" }` | `kind` |
|
|
84
|
-
| Header | `{ kind: "header", title }` | `kind`, `title` |
|
|
85
|
-
|
|
86
|
-
Page items also accept:
|
|
87
|
-
|
|
88
|
-
- `to` — internal route (TanStack Router link, type-safe)
|
|
89
|
-
- `href` — external URL (opens in new tab)
|
|
90
|
-
- `params`, `search` — TanStack Router link params
|
|
91
|
-
- `children` — nested navigation items (sub-menu)
|
|
92
|
-
- `hidden` — conditionally hide the item
|
|
93
|
-
|
|
94
|
-
### Nested navigation
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
const navigation: Navigation = [
|
|
98
|
-
{
|
|
99
|
-
title: "Admin",
|
|
100
|
-
icon: <AdminIcon />,
|
|
101
|
-
to: "/admin",
|
|
102
|
-
children: [
|
|
103
|
-
{ title: "Users", icon: <PeopleIcon />, to: "/admin/users" },
|
|
104
|
-
{ title: "Roles", icon: <SecurityIcon />, to: "/admin/roles" },
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
];
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### LayoutOptions
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
interface LayoutOptions {
|
|
114
|
-
showShell?: boolean; // Show AppBar + NavigationRail (default: true)
|
|
115
|
-
snackbarOrigin?: SnackbarOrigin; // Notification position
|
|
116
|
-
publicRoutes?: string[]; // Routes that skip authentication
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Theme configuration
|
|
121
|
-
|
|
122
|
-
The theme is pre-configured in the template at `src/lib/theme.ts`. Use MUI's `extendTheme` to customize:
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
// src/lib/theme.ts
|
|
126
|
-
import { extendTheme } from "@mui/material/styles";
|
|
127
|
-
|
|
128
|
-
export const theme = extendTheme({
|
|
129
|
-
colorSchemes: {
|
|
130
|
-
light: { palette: { primary: { main: "#1976d2" } } },
|
|
131
|
-
dark: { palette: { primary: { main: "#90caf9" } } },
|
|
132
|
-
},
|
|
133
|
-
colorSchemeSelector: "data-mui-color-scheme",
|
|
134
|
-
});
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Dark mode styling
|
|
138
|
-
|
|
139
|
-
Always use `theme.applyStyles("dark", ...)` for mode-specific styling:
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
<Box
|
|
143
|
-
sx={(theme) => ({
|
|
144
|
-
backgroundColor: "#fff",
|
|
145
|
-
...theme.applyStyles("dark", {
|
|
146
|
-
backgroundColor: "#121212",
|
|
147
|
-
}),
|
|
148
|
-
})}
|
|
149
|
-
/>
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### i18n setup
|
|
153
|
-
|
|
154
|
-
Locale files in `src/locales/` are auto-detected by `viteWczLayout()`:
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
src/locales/en.json # English (default)
|
|
158
|
-
src/locales/cs.json # Czech
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
The Vite plugin generates the `virtual:wcz-layout` module with all locale resources. `LayoutProvider` initializes `i18next` with language detection (cookie-based, 1-year expiry) and syncs Zod and DayJS locales on language change.
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
import { useTranslation } from "react-i18next";
|
|
165
|
-
|
|
166
|
-
function MyComponent() {
|
|
167
|
-
const { t } = useTranslation();
|
|
168
|
-
return <Typography>{t("welcome")}</Typography>;
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### Root route head
|
|
173
|
-
|
|
174
|
-
`rootRouteHead` provides meta tags and manifest link:
|
|
175
|
-
|
|
176
|
-
```typescript
|
|
177
|
-
export const Route = createRootRouteWithContext()({
|
|
178
|
-
head: rootRouteHead,
|
|
179
|
-
// optionally: head: rootRouteHead({ manifest: "/manifest.json" }),
|
|
180
|
-
});
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### Service worker
|
|
184
|
-
|
|
185
|
-
`LayoutProvider` registers `/sw.js` on mount for PWA support.
|
|
186
|
-
|
|
187
|
-
## Common Mistakes
|
|
188
|
-
|
|
189
|
-
### HIGH Using theme.palette for dark mode instead of theme.applyStyles
|
|
190
|
-
|
|
191
|
-
Wrong:
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
sx={{ color: mode === "dark" ? "white" : "black" }}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Correct:
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
sx={(theme) => ({
|
|
201
|
-
color: "black",
|
|
202
|
-
...theme.applyStyles("dark", { color: "white" }),
|
|
203
|
-
})}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
The app uses `colorSchemeSelector: "data-mui-color-scheme"`. Mode-specific styles must use `theme.applyStyles("dark", ...)`, not conditional palette checks or mode variables.
|
|
207
|
-
|
|
208
|
-
Source: copilot-instructions.md / wcz-layout:src/lib/theme.ts
|
|
209
|
-
|
|
210
|
-
Cross-skill: See also skills/ui-pages/SKILL.md § Common Mistakes
|
|
211
|
-
|
|
212
|
-
### HIGH Using href for internal routes in Navigation
|
|
213
|
-
|
|
214
|
-
Wrong:
|
|
215
|
-
|
|
216
|
-
```typescript
|
|
217
|
-
{ title: "Todos", icon: <ListIcon />, href: "/todos" }
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
Correct:
|
|
221
|
-
|
|
222
|
-
```typescript
|
|
223
|
-
{ title: "Todos", icon: <ListIcon />, to: "/todos" }
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
`NavigationPageItem` supports dual destination: `to` for internal TanStack Router links, `href` for external URLs. Using `href` for internal routes causes full page reloads.
|
|
227
|
-
|
|
228
|
-
Source: wcz-layout:src/models/Navigation.ts
|
|
229
|
-
|
|
230
|
-
### MEDIUM Creating NavigationPageItem without icon
|
|
231
|
-
|
|
232
|
-
Wrong:
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
{ title: "Todos", to: "/todos" }
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Correct:
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
{ title: "Todos", icon: <ListIcon />, to: "/todos" }
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
`NavigationPageItem` requires both `title` and `icon`. Missing `icon` renders an empty slot in the NavigationRail.
|
|
245
|
-
|
|
246
|
-
Source: wcz-layout:src/models/Navigation.ts
|
|
247
|
-
|
|
248
|
-
### MEDIUM Adding locale without updating supportedLngs
|
|
249
|
-
|
|
250
|
-
This is actually **not** an issue — the Vite plugin auto-detects locale files in `src/locales/`. Adding a new JSON file (e.g. `de.json`) is sufficient; `supportedLngs` is derived from `Object.keys(resources)` automatically.
|
|
251
|
-
|
|
252
|
-
Source: wcz-layout:src/providers/LayoutProvider.tsx
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
See also:
|
|
257
|
-
|
|
258
|
-
- skills/ui-pages/SKILL.md — Pages render inside the layout shell.
|
|
259
|
-
- skills/auth/SKILL.md — publicRoutes bypass auth; permission guards need auth context.
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: project-initialization
|
|
3
|
-
description: >
|
|
4
|
-
Scaffold a new wcz-layout project from the template. Covers placeholder
|
|
5
|
-
replacement across three naming conventions (kebab-case, Title Case,
|
|
6
|
-
snake_case), .env and .env.local configuration, VITE_ENTRA_CLIENT_ID,
|
|
7
|
-
VITE_APP_TITLE, VITE_MUI_LICENSE_KEY, favicon generation via favicon.io,
|
|
8
|
-
and viteWczLayout() Vite plugin registration. Activate when starting a
|
|
9
|
-
new internal project or configuring environment variables.
|
|
10
|
-
type: lifecycle
|
|
11
|
-
library: wcz-layout
|
|
12
|
-
library_version: "7.6.1"
|
|
13
|
-
sources:
|
|
14
|
-
- "wcz-layout:src/env.ts"
|
|
15
|
-
- "wcz-layout:src/lib/vite-plugin.ts"
|
|
16
|
-
- "wcz-layout:vite.config.ts"
|
|
17
|
-
- "wcz-layout:.env"
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
# Project Initialization
|
|
21
|
-
|
|
22
|
-
## Setup
|
|
23
|
-
|
|
24
|
-
After cloning the template repository, initialize the project in this order:
|
|
25
|
-
|
|
26
|
-
### 1. Replace all template placeholders
|
|
27
|
-
|
|
28
|
-
Use your editor's replace-all (across all files) for each naming convention:
|
|
29
|
-
|
|
30
|
-
| Find | Replace with | Used in |
|
|
31
|
-
| -------------- | ------------ | ------------------------------------------------ |
|
|
32
|
-
| `project-name` | `app-store` | package.json name, URLs, CSS classes |
|
|
33
|
-
| `Project Name` | `App Store` | VITE_APP_TITLE, display strings, docs |
|
|
34
|
-
| `my_app` | `app_store` | Database names, env vars, snake_case identifiers |
|
|
35
|
-
|
|
36
|
-
### 2. Configure environment variables
|
|
37
|
-
|
|
38
|
-
```sh
|
|
39
|
-
# .env — committed to repo, shared across team
|
|
40
|
-
VITE_ENTRA_CLIENT_ID=your-entra-client-id
|
|
41
|
-
VITE_ENTRA_TENANT_ID=your-tenant-id
|
|
42
|
-
VITE_APP_TITLE=App Store
|
|
43
|
-
VITE_MUI_LICENSE_KEY=your-mui-license-key
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Create `.env.local` (gitignored) for local secret overrides:
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
# .env.local — never committed
|
|
50
|
-
VITE_ENTRA_CLIENT_ID=your-dev-client-id
|
|
51
|
-
ENTRA_CLIENT_ID=your-server-client-id
|
|
52
|
-
ENTRA_TENANT_ID=your-server-tenant-id
|
|
53
|
-
ENTRA_CLIENT_SECRET=your-client-secret
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### 3. Generate favicon
|
|
57
|
-
|
|
58
|
-
Go to https://favicon.io/favicon-converter/, upload your logo, and place the generated files in `public/`.
|
|
59
|
-
|
|
60
|
-
### 4. Verify Vite plugin registration
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
// vite.config.ts
|
|
64
|
-
import { viteWczLayout } from "wcz-layout/vite";
|
|
65
|
-
|
|
66
|
-
export default defineConfig({
|
|
67
|
-
plugins: [
|
|
68
|
-
tanstackStart(),
|
|
69
|
-
nitro(),
|
|
70
|
-
viteReact(),
|
|
71
|
-
babel(reactCompilerPreset()),
|
|
72
|
-
checker({ typescript: true }),
|
|
73
|
-
viteWczLayout(),
|
|
74
|
-
],
|
|
75
|
-
});
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Core Patterns
|
|
79
|
-
|
|
80
|
-
### Environment validation with createEnv
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// src/env.ts
|
|
84
|
-
import { createEnv } from "wcz-layout/utils";
|
|
85
|
-
import { z } from "zod";
|
|
86
|
-
|
|
87
|
-
export const clientEnv = createEnv({
|
|
88
|
-
clientPrefix: "VITE_",
|
|
89
|
-
client: {
|
|
90
|
-
VITE_ENTRA_CLIENT_ID: z.string(),
|
|
91
|
-
VITE_ENTRA_TENANT_ID: z.string(),
|
|
92
|
-
VITE_APP_TITLE: z.string(),
|
|
93
|
-
VITE_MUI_LICENSE_KEY: z.string(),
|
|
94
|
-
},
|
|
95
|
-
runtimeEnv: import.meta.env,
|
|
96
|
-
emptyStringAsUndefined: true,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
export const serverEnv = createEnv({
|
|
100
|
-
server: {
|
|
101
|
-
ENTRA_CLIENT_ID: z.string(),
|
|
102
|
-
ENTRA_TENANT_ID: z.string(),
|
|
103
|
-
ENTRA_CLIENT_SECRET: z.string(),
|
|
104
|
-
},
|
|
105
|
-
runtimeEnv: process.env,
|
|
106
|
-
emptyStringAsUndefined: true,
|
|
107
|
-
});
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Vault secrets for local development
|
|
111
|
-
|
|
112
|
-
The `viteWczLayout()` plugin automatically loads secrets from HashiCorp Vault during `vite dev` if `VAULT_ADDRESS`, `VAULT_USERNAME`, `VAULT_PASSWORD`, and `VAULT_SECRET_PATH` are set in `.env`. Vault-loaded secrets only populate `process.env` keys that are not already set.
|
|
113
|
-
|
|
114
|
-
## Common Mistakes
|
|
115
|
-
|
|
116
|
-
### CRITICAL Missing viteWczLayout() in Vite config
|
|
117
|
-
|
|
118
|
-
Wrong:
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// vite.config.ts
|
|
122
|
-
export default defineConfig({
|
|
123
|
-
plugins: [tanstackStart(), viteReact()],
|
|
124
|
-
});
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Correct:
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
// vite.config.ts
|
|
131
|
-
import { viteWczLayout } from "wcz-layout/vite";
|
|
132
|
-
export default defineConfig({
|
|
133
|
-
plugins: [tanstackStart(), viteReact(), viteWczLayout()],
|
|
134
|
-
});
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Without `viteWczLayout()`, the `virtual:wcz-layout` module won't resolve, breaking i18n resources, permissions, and scopes at build time.
|
|
138
|
-
|
|
139
|
-
Source: wcz-layout:src/lib/vite-plugin.ts
|
|
140
|
-
|
|
141
|
-
### HIGH Forgetting .env.local for local secrets
|
|
142
|
-
|
|
143
|
-
`.env` is committed to the repo and shared across the team. Local overrides (Entra client IDs, secrets) must go in `.env.local` which is gitignored. Committing secrets to `.env` exposes them in the repository.
|
|
144
|
-
|
|
145
|
-
Source: maintainer interview
|
|
146
|
-
|
|
147
|
-
### HIGH Not replacing all naming conventions
|
|
148
|
-
|
|
149
|
-
The template uses three naming conventions. Use replace-all across the entire project for each one. Missing any convention leaves stale references in package.json, env files, or source imports.
|
|
150
|
-
|
|
151
|
-
Source: maintainer interview
|
|
152
|
-
|
|
153
|
-
### MEDIUM Empty string env vars pass silently
|
|
154
|
-
|
|
155
|
-
Wrong:
|
|
156
|
-
|
|
157
|
-
```sh
|
|
158
|
-
# .env
|
|
159
|
-
VITE_APP_TITLE=
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
Correct:
|
|
163
|
-
|
|
164
|
-
```sh
|
|
165
|
-
# .env
|
|
166
|
-
VITE_APP_TITLE=My Application
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
`createEnv` uses `emptyStringAsUndefined: true`, so empty strings become `undefined` and fail Zod validation at runtime, not build time.
|
|
170
|
-
|
|
171
|
-
Source: wcz-layout:src/env.ts
|
|
172
|
-
|
|
173
|
-
### HIGH Tension: Simplicity vs. full-stack rigor
|
|
174
|
-
|
|
175
|
-
This skill's simple setup conflicts with production readiness from api-routes. Agents scaffolding a quick prototype may skip authorizationMiddleware and validationMiddleware, producing code that works locally but is insecure in production. Always include middleware from the start.
|
|
176
|
-
|
|
177
|
-
See also: skills/api-routes/SKILL.md § Common Mistakes
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
See also: skills/project-structure/SKILL.md — After init, understand where to place new code.
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: project-structure
|
|
3
|
-
description: >
|
|
4
|
-
Domain-scoped co-location rules for TanStack Router file-based routing.
|
|
5
|
-
Dash-prefix convention (-components/, -hooks/) excludes folders from
|
|
6
|
-
route generation. Domain-specific code lives in route folders; shared
|
|
7
|
-
code in src/components/, src/hooks/. Drizzle schemas in src/lib/db/schemas/,
|
|
8
|
-
Zod schemas in src/schemas/, collections in src/lib/db/collections/.
|
|
9
|
-
Activate when deciding where to place a new file or organizing a feature.
|
|
10
|
-
type: lifecycle
|
|
11
|
-
library: wcz-layout
|
|
12
|
-
library_version: "7.6.1"
|
|
13
|
-
requires:
|
|
14
|
-
- project-initialization
|
|
15
|
-
sources:
|
|
16
|
-
- "wcz-layout:src/routeTree.gen.ts"
|
|
17
|
-
- "TanStack/router:docs/router/api/file-based-routing.md"
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
This skill builds on project-initialization. Read it first for env and plugin setup.
|
|
21
|
-
|
|
22
|
-
# Project Structure Conventions
|
|
23
|
-
|
|
24
|
-
## Setup
|
|
25
|
-
|
|
26
|
-
A typical project follows this structure:
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
src/
|
|
30
|
-
├── components/ # SHARED components (used across multiple domains)
|
|
31
|
-
│ ├── StyledCard.tsx
|
|
32
|
-
│ └── DataGridToolbar.tsx
|
|
33
|
-
├── hooks/ # SHARED hooks
|
|
34
|
-
├── lib/
|
|
35
|
-
│ ├── auth/
|
|
36
|
-
│ │ ├── permissions.ts # AD group → permission key mapping
|
|
37
|
-
│ │ └── scopes.ts # OAuth scope definitions
|
|
38
|
-
│ ├── db/
|
|
39
|
-
│ │ ├── schemas/ # Drizzle pgTable definitions
|
|
40
|
-
│ │ │ └── todo.ts
|
|
41
|
-
│ │ ├── collections/ # TanStack DB collections
|
|
42
|
-
│ │ │ └── todoCollection.ts
|
|
43
|
-
│ │ └── migrations/ # Drizzle migrations
|
|
44
|
-
│ └── theme.ts
|
|
45
|
-
├── middleware/ # Custom middleware (e.g. databaseMiddleware)
|
|
46
|
-
├── schemas/ # Zod validation schemas (derived from Drizzle)
|
|
47
|
-
│ └── todo.ts
|
|
48
|
-
├── locales/ # i18n translation files
|
|
49
|
-
│ ├── en.json
|
|
50
|
-
│ └── cs.json
|
|
51
|
-
├── routes/
|
|
52
|
-
│ ├── __root.tsx # Root layout with LayoutProvider
|
|
53
|
-
│ ├── index.tsx # Home page
|
|
54
|
-
│ ├── api/
|
|
55
|
-
│ │ ├── health.ts # Health check endpoint
|
|
56
|
-
│ │ └── todos/
|
|
57
|
-
│ │ ├── index.ts # GET / POST handlers
|
|
58
|
-
│ │ └── $id.ts # GET / PUT / DELETE by id
|
|
59
|
-
│ └── todos/
|
|
60
|
-
│ ├── -components/ # Domain-specific components (dash prefix!)
|
|
61
|
-
│ │ └── Form.tsx
|
|
62
|
-
│ ├── index.tsx # List page
|
|
63
|
-
│ ├── create.tsx # Create page
|
|
64
|
-
│ ├── $id.tsx # Detail page
|
|
65
|
-
│ └── edit.$id.tsx # Edit page
|
|
66
|
-
├── env.ts # Environment variable validation
|
|
67
|
-
├── router.tsx # Router configuration
|
|
68
|
-
├── start.ts # TanStack Start configuration
|
|
69
|
-
└── routeTree.gen.ts # AUTO-GENERATED — never edit
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Core Patterns
|
|
73
|
-
|
|
74
|
-
### Domain-scoped co-location
|
|
75
|
-
|
|
76
|
-
When a component, hook, or utility is used only within one route domain, place it inside that route folder with a dash-prefix directory:
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
src/routes/todos/-components/Form.tsx # Only used in /todos routes
|
|
80
|
-
src/routes/todos/-hooks/useTodoFilters.ts # Only used in /todos routes
|
|
81
|
-
src/routes/orders/-components/OrderCard.tsx
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
When the same component is reused across multiple domains, move it to the top-level shared directory:
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
src/components/StyledCard.tsx # Used by both /todos and /orders
|
|
88
|
-
src/hooks/useDebounce.ts # Used everywhere
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Data layer file placement
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
src/lib/db/schemas/todo.ts # Drizzle table: pgTable("todos", { ... })
|
|
95
|
-
src/schemas/todo.ts # Zod schema: createSelectSchema(todoTable, { ... })
|
|
96
|
-
src/lib/db/collections/todoCollection.ts # TanStack DB collection
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Common Mistakes
|
|
100
|
-
|
|
101
|
-
### CRITICAL Missing dash prefix on route-scoped folders
|
|
102
|
-
|
|
103
|
-
Wrong:
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
src/routes/todos/components/Form.tsx
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Correct:
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
src/routes/todos/-components/Form.tsx
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
TanStack Router treats folders without a dash prefix as route segments. `components/Form.tsx` becomes a `/todos/components/Form` route. The dash prefix (`-components/`) tells the router to ignore this directory.
|
|
116
|
-
|
|
117
|
-
Source: TanStack/router:docs/router/api/file-based-routing.md
|
|
118
|
-
|
|
119
|
-
### CRITICAL Editing routeTree.gen.ts
|
|
120
|
-
|
|
121
|
-
Wrong:
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
// src/routeTree.gen.ts — manually adding a route
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Correct:
|
|
128
|
-
|
|
129
|
-
Create a new file in `src/routes/` and let TanStack Router auto-generate the route tree.
|
|
130
|
-
|
|
131
|
-
This file is auto-generated and overwritten on every route change. Manual edits are silently lost.
|
|
132
|
-
|
|
133
|
-
Source: wcz-layout:src/routeTree.gen.ts header comment
|
|
134
|
-
|
|
135
|
-
### HIGH Placing domain-specific component in src/components/
|
|
136
|
-
|
|
137
|
-
Wrong:
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
// src/components/TodoForm.tsx — only used in /todos routes
|
|
141
|
-
export function TodoForm() {
|
|
142
|
-
return <form>...</form>;
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Correct:
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
// src/routes/todos/-components/TodoForm.tsx
|
|
150
|
-
export function TodoForm() {
|
|
151
|
-
return <form>...</form>;
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Components used only within one route domain should live in that domain's `-components/` folder. `src/components/` is reserved for components shared across multiple domains.
|
|
156
|
-
|
|
157
|
-
Source: maintainer interview
|