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,270 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: tanstack-db-collections
|
|
3
|
-
description: >
|
|
4
|
-
Wire TanStack DB createCollection with queryCollectionOptions for optimistic
|
|
5
|
-
client-side data. Set up axios instance with MSAL token interceptor using
|
|
6
|
-
getAccessToken(scopeKey). CRUD mutation handlers (onInsert, onUpdate, onDelete).
|
|
7
|
-
Use shared queryClient from wcz-layout/query. Consume with useLiveQuery /
|
|
8
|
-
useLiveSuspenseQuery. Activate when creating or modifying data collections.
|
|
9
|
-
type: core
|
|
10
|
-
library: wcz-layout
|
|
11
|
-
library_version: "7.6.1"
|
|
12
|
-
requires:
|
|
13
|
-
- database-schema
|
|
14
|
-
- api-routes
|
|
15
|
-
sources:
|
|
16
|
-
- "wcz-layout:src/lib/db/collections/"
|
|
17
|
-
- "wcz-layout:src/exports/query.ts"
|
|
18
|
-
- "wcz-layout:src/lib/queryClient.ts"
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
# TanStack DB Collections
|
|
22
|
-
|
|
23
|
-
## Setup
|
|
24
|
-
|
|
25
|
-
Collection files live in `src/lib/db/collections/`:
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
src/lib/db/collections/todoCollection.ts
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Core Patterns
|
|
32
|
-
|
|
33
|
-
### Creating a collection
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
// src/lib/db/collections/todoCollection.ts
|
|
37
|
-
import { createCollection } from "@tanstack/react-db";
|
|
38
|
-
import { queryCollectionOptions } from "@tanstack/query-db-collection";
|
|
39
|
-
import axios from "axios";
|
|
40
|
-
import { getAccessToken, queryClient } from "wcz-layout";
|
|
41
|
-
import { TodoSchema } from "~/schemas/todo";
|
|
42
|
-
import type { Todo } from "~/schemas/todo";
|
|
43
|
-
|
|
44
|
-
const api = axios.create({ baseURL: "/api/todos" });
|
|
45
|
-
|
|
46
|
-
api.interceptors.request.use(async (config) => {
|
|
47
|
-
const accessToken = await getAccessToken("api");
|
|
48
|
-
config.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
49
|
-
return config;
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
export const todoCollection = createCollection<Todo>({
|
|
53
|
-
id: "todos",
|
|
54
|
-
getKey: (item) => item.id,
|
|
55
|
-
...queryCollectionOptions({
|
|
56
|
-
queryClient,
|
|
57
|
-
queryKey: ["todos"],
|
|
58
|
-
queryFn: async () => {
|
|
59
|
-
const response = await api.get<Todo[]>("");
|
|
60
|
-
return response.data;
|
|
61
|
-
},
|
|
62
|
-
schema: TodoSchema,
|
|
63
|
-
onInsert: async (item) => {
|
|
64
|
-
await api.post("", item);
|
|
65
|
-
},
|
|
66
|
-
onUpdate: async (item) => {
|
|
67
|
-
await api.put(`/${item.id}`, item);
|
|
68
|
-
},
|
|
69
|
-
onDelete: async (item) => {
|
|
70
|
-
await api.delete(`/${item.id}`);
|
|
71
|
-
},
|
|
72
|
-
}),
|
|
73
|
-
});
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Choosing the scope key
|
|
77
|
-
|
|
78
|
-
The `getAccessToken(scopeKey)` argument depends on the target service:
|
|
79
|
-
|
|
80
|
-
| Target | Scope key | When |
|
|
81
|
-
| ------------------- | --------- | --------------------------------- |
|
|
82
|
-
| Your own API routes | `"api"` | Default — calls to `/api/*` |
|
|
83
|
-
| Microsoft Graph | `"graph"` | Calls to `graph.microsoft.com` |
|
|
84
|
-
| File microservice | `"file"` | Calls to the file storage service |
|
|
85
|
-
|
|
86
|
-
Scope keys are defined in your app's `src/lib/auth/scopes.ts`.
|
|
87
|
-
|
|
88
|
-
### Consuming collection data in components
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
import { useLiveQuery } from "@tanstack/react-db";
|
|
92
|
-
import { todoCollection } from "~/lib/db/collections/todoCollection";
|
|
93
|
-
|
|
94
|
-
function TodoList() {
|
|
95
|
-
const { data: todos } = useLiveQuery((query) =>
|
|
96
|
-
query.from({ todos: todoCollection })
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<ul>
|
|
101
|
-
{todos.map((todo) => (
|
|
102
|
-
<li key={todo.id}>{todo.name}</li>
|
|
103
|
-
))}
|
|
104
|
-
</ul>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
For Suspense boundaries, use `useLiveSuspenseQuery` instead.
|
|
110
|
-
|
|
111
|
-
### Optimistic mutations
|
|
112
|
-
|
|
113
|
-
Collections handle mutations optimistically. Insert, update, and delete operations update the local collection immediately and sync to the server via `onInsert`, `onUpdate`, `onDelete`:
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
import { todoCollection } from "~/lib/db/collections/todoCollection";
|
|
117
|
-
import { uuidv7 } from "uuidv7";
|
|
118
|
-
|
|
119
|
-
// Insert — collection updates instantly, API call runs in background
|
|
120
|
-
todoCollection.insert({
|
|
121
|
-
id: uuidv7(),
|
|
122
|
-
name: "New task",
|
|
123
|
-
isCompleted: false,
|
|
124
|
-
createdBy: user.email,
|
|
125
|
-
createdAt: new Date(),
|
|
126
|
-
updatedAt: new Date(),
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Update
|
|
130
|
-
todoCollection.update({ ...existingTodo, name: "Updated name" });
|
|
131
|
-
|
|
132
|
-
// Delete
|
|
133
|
-
todoCollection.delete(existingTodo);
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Common Mistakes
|
|
137
|
-
|
|
138
|
-
### CRITICAL Using useQuery / useSuspenseQuery instead of TanStack DB
|
|
139
|
-
|
|
140
|
-
Wrong:
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
import { useQuery } from "@tanstack/react-query";
|
|
144
|
-
|
|
145
|
-
const { data } = useQuery({
|
|
146
|
-
queryKey: ["todos"],
|
|
147
|
-
queryFn: fetchTodos,
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Correct:
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
import { useLiveQuery } from "@tanstack/react-db";
|
|
155
|
-
import { todoCollection } from "~/lib/db/collections/todoCollection";
|
|
156
|
-
|
|
157
|
-
const { data } = useLiveQuery((query) => query.from({ todos: todoCollection }));
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
The enforced pattern is TanStack DB collections with `useLiveQuery` / `useLiveSuspenseQuery` for all API data. Plain TanStack Query bypasses the optimistic update layer.
|
|
161
|
-
|
|
162
|
-
Source: maintainer interview
|
|
163
|
-
|
|
164
|
-
### CRITICAL Creating a new QueryClient instead of using shared one
|
|
165
|
-
|
|
166
|
-
Wrong:
|
|
167
|
-
|
|
168
|
-
```typescript
|
|
169
|
-
import { QueryClient } from "@tanstack/react-query";
|
|
170
|
-
const queryClient = new QueryClient();
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Correct:
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
import { queryClient } from "wcz-layout/query";
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
wcz-layout exports a singleton `queryClient`. Creating a new one breaks cache sharing and SSR query integration.
|
|
180
|
-
|
|
181
|
-
Source: wcz-layout:src/lib/queryClient.ts
|
|
182
|
-
|
|
183
|
-
### HIGH Forgetting token interceptor on axios instance
|
|
184
|
-
|
|
185
|
-
Wrong:
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
const api = axios.create({ baseURL: "/api/todos" });
|
|
189
|
-
// No interceptor — requests will return 401
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Correct:
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
import { getAccessToken } from "wcz-layout/utils";
|
|
196
|
-
|
|
197
|
-
const api = axios.create({ baseURL: "/api/todos" });
|
|
198
|
-
api.interceptors.request.use(async (config) => {
|
|
199
|
-
const accessToken = await getAccessToken("api");
|
|
200
|
-
config.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
201
|
-
return config;
|
|
202
|
-
});
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
API endpoints require Bearer token authentication. Without the interceptor, all requests return 401.
|
|
206
|
-
|
|
207
|
-
Source: consumer project example
|
|
208
|
-
|
|
209
|
-
### HIGH Using getKey that returns non-unique values
|
|
210
|
-
|
|
211
|
-
Wrong:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
export const todoCollection = createCollection<Todo>({
|
|
215
|
-
id: "todos",
|
|
216
|
-
getKey: (item) => item.name, // names can be duplicated!
|
|
217
|
-
...queryCollectionOptions({ ... }),
|
|
218
|
-
});
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Correct:
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
export const todoCollection = createCollection<Todo>({
|
|
225
|
-
id: "todos",
|
|
226
|
-
getKey: (item) => item.id, // uuid — always unique
|
|
227
|
-
...queryCollectionOptions({ ... }),
|
|
228
|
-
});
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
`getKey` must return a unique identifier for each item. Using a non-unique field causes silent collection corruption — items overwrite each other.
|
|
232
|
-
|
|
233
|
-
Source: TanStack DB documentation
|
|
234
|
-
|
|
235
|
-
### MEDIUM Forgetting schema option in queryCollectionOptions
|
|
236
|
-
|
|
237
|
-
Wrong:
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
...queryCollectionOptions({
|
|
241
|
-
queryClient,
|
|
242
|
-
queryKey: ["todos"],
|
|
243
|
-
queryFn: async () => (await api.get("")).data,
|
|
244
|
-
// no schema — API responses are not validated
|
|
245
|
-
}),
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
Correct:
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
...queryCollectionOptions({
|
|
252
|
-
queryClient,
|
|
253
|
-
queryKey: ["todos"],
|
|
254
|
-
queryFn: async () => (await api.get("")).data,
|
|
255
|
-
schema: TodoSchema,
|
|
256
|
-
}),
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
The `schema` option enables runtime validation of API responses. Omitting it removes type safety on incoming data — malformed payloads pass silently.
|
|
260
|
-
|
|
261
|
-
Source: consumer project example
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
See also:
|
|
266
|
-
|
|
267
|
-
- skills/ui-pages/SKILL.md — Pages consume collections via useLiveQuery.
|
|
268
|
-
- skills/database-schema/SKILL.md — Same Zod schema used as collection schema.
|
|
269
|
-
- skills/api-routes/SKILL.md — Collections consume API routes via axios baseURL.
|
|
270
|
-
- skills/data-grid/SKILL.md — Grid rows come from useLiveQuery results.
|
package/skills/template-init.md
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "Initializes a new app from this project template"
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
You are an AI assistant helping a developer initialize a new project from a template.
|
|
6
|
-
|
|
7
|
-
Follow these steps in exact order.
|
|
8
|
-
|
|
9
|
-
1. GIT BRANCH CHECK
|
|
10
|
-
Autonomously check if the current git branch is named `master` (you can execute `git branch --show-current` in the terminal to check).
|
|
11
|
-
- If the branch is `master`: Proceed directly to step 2.
|
|
12
|
-
- If the branch is not `master` (e.g., it is `main`): Execute the command `git branch -m main master` in the terminal to rename it. Wait for the command to finish before proceeding to step 2.
|
|
13
|
-
|
|
14
|
-
2. DEPENDENCY CHECK
|
|
15
|
-
Check the project root for the presence of a `node_modules` directory.
|
|
16
|
-
- If `node_modules` is found: Proceed directly to step 3.
|
|
17
|
-
- If `node_modules` is not found: Output this exact message to the user: "I see `node_modules` is missing. I am running your install script to get your dependencies set up. This might take a moment..."
|
|
18
|
-
Then, execute the command `npm run npm:install` in the terminal. Wait for the installation process to complete fully before proceeding to step 3.
|
|
19
|
-
|
|
20
|
-
3. ENVIRONMENT SETUP
|
|
21
|
-
Search the workspace root for a `.env.local` file.
|
|
22
|
-
- If found: Proceed directly to step 4.
|
|
23
|
-
- If not found: Use your edit tools to create `.env.local` in the root directory and populate it with the following initial keys:
|
|
24
|
-
|
|
25
|
-
VAULT_ADDRESS=https://vault-dev.wistron.com:8200/
|
|
26
|
-
VAULT_SECRET_PATH=wcz/sat-qas-01/project-name-wcz/default
|
|
27
|
-
VAULT_USERNAME=
|
|
28
|
-
VAULT_PASSWORD=
|
|
29
|
-
|
|
30
|
-
DATABASE_URL=
|
|
31
|
-
|
|
32
|
-
Once created, let the user know and proceed to step 4.
|
|
33
|
-
|
|
34
|
-
4. PROJECT NAME REPLACEMENT
|
|
35
|
-
Ask the user: "What is your project name?"
|
|
36
|
-
Once the user provides the name, standardise it across the project workspace. You will use your search and edit tools across the entire workspace for placeholder strings and replace them with the user's input, matching the exact casing conventions.
|
|
37
|
-
|
|
38
|
-
For example, if the user inputs "Dev Portal":
|
|
39
|
-
- Replace "project-name" with "dev-portal" (kebab-case)
|
|
40
|
-
- Replace "Project Name" with "Dev Portal" (Title Case)
|
|
41
|
-
|
|
42
|
-
Execute this workspace-wide search and replace. Once completed, tell the user how many files were updated, and proceed to step 5.
|
|
43
|
-
|
|
44
|
-
5. DATABASE SETUP
|
|
45
|
-
Ask the user: "Do you already have a Postgres database created?"
|
|
46
|
-
Wait for the user's response.
|
|
47
|
-
- If the user answers "yes":
|
|
48
|
-
Ask the user: "Please enter your database schema name (e.g., 'dev_portal')."
|
|
49
|
-
Wait for their input. Once provided, **use your file edit tool** to update the `.env` file by setting `DATABASE_SCHEMA=[user-input]`. Then proceed to step 6.
|
|
50
|
-
- If the user answers "no":
|
|
51
|
-
Ask the user: "Would you like me to create a local database using Docker?"
|
|
52
|
-
Wait for the user's response.
|
|
53
|
-
- If the user answers "yes": Execute the following command in the terminal:
|
|
54
|
-
`docker run --name drizzle-postgres -e POSTGRES_PASSWORD=admin -d -p 5432:5432 postgres`
|
|
55
|
-
- If the command succeeds: **Use your file edit tool** to update the `.env.local` file to set `DATABASE_URL=postgres://postgres:admin@localhost:5432/postgres`. Then proceed to step 6.
|
|
56
|
-
- If the command fails: Output: "It looks like the Docker command failed. Please ensure Docker Desktop is running. Let me know when you are ready to try again, or type 'no' to skip this step." Wait for the user to try again or skip, then proceed accordingly.
|
|
57
|
-
- If the user answers "no" to creating a local database: Proceed to step 6.
|
|
58
|
-
|
|
59
|
-
6. ENTRA ID SETUP
|
|
60
|
-
This step has three parts. Complete them sequentially, waiting for the user at each prompt.
|
|
61
|
-
|
|
62
|
-
**Part A: Application Creation**
|
|
63
|
-
Ask the user: "Is the Entra ID application already created for this project? (yes/no)"
|
|
64
|
-
- If "no": Output this instruction:
|
|
65
|
-
"Please navigate to https://itsr.wistron.com/homepage/apply and request the application creation.
|
|
66
|
-
Path: ITSR Apply -> Service Apply -> Select Service Type -> Azure AD - Application Management - Add or Modify -> fill the information.
|
|
67
|
-
Since application approval takes time, we will skip the rest of the Entra ID configuration for now. Please type 'continue' once you have submitted the request."
|
|
68
|
-
Wait for the user to type "continue". Once they do, **SKIP Part B and Part C entirely**, and proceed directly to step 7.
|
|
69
|
-
- If "yes": Proceed to Part B.
|
|
70
|
-
|
|
71
|
-
**Part B: Application Configuration**
|
|
72
|
-
Ask the user: "Have you configured the created Entra ID application settings? (yes/no)"
|
|
73
|
-
- If "no": Output this configuration guide:
|
|
74
|
-
"Please navigate to https://entra.microsoft.com/ and configure your application using these steps:
|
|
75
|
-
|
|
76
|
-
**App Registrations:**
|
|
77
|
-
1. **Authentication:** Add 'Single-page application' and configure your Redirect URIs.
|
|
78
|
-
2. **Token Configuration:** Add groups claims -> Security groups. Ensure ID, Access, and SAML token properties have checked `sAMAccountName`.
|
|
79
|
-
3. **Expose an API:** Set the Application ID URI. Add a Scope named `access_as_user` (Admins and users) with appropriate display names/descriptions.
|
|
80
|
-
4. **Owners:** Add other developers as owners.
|
|
81
|
-
5. **Manifest:** Update the `api` object properties to `"acceptMappedClaims": true` and `"requestedAccessTokenVersion": 2`.
|
|
82
|
-
|
|
83
|
-
**Enterprise Applications:**
|
|
84
|
-
1. Search for your application.
|
|
85
|
-
2. **Single Sign-on:** Add custom claims for `employeeId` (Source: user.extensionattribute5) and `department` (Source: user.department). Optionally, add `employeeCategory` (Source: user.extensionattribute13) and `companyName` (Source: user.companyname).
|
|
86
|
-
3. **Owners:** Add other developers as owners.
|
|
87
|
-
|
|
88
|
-
Once you have finished these steps, type 'continue'."
|
|
89
|
-
Wait for the user to type "continue".
|
|
90
|
-
|
|
91
|
-
- If "yes" or once they continue: Proceed to Part C.
|
|
92
|
-
|
|
93
|
-
**Part C: Credentials**
|
|
94
|
-
Ask the user: "Please provide the CLIENT_ID and CLIENT_SECRET for your Entra ID application."
|
|
95
|
-
Wait for the user's input.
|
|
96
|
-
Once provided:
|
|
97
|
-
1. **Use your file edit tool** to update the `.env` file. Find the `VITE_ENTRA_CLIENT_ID=` line and set it to the provided Client ID (e.g., `VITE_ENTRA_CLIENT_ID=[user-provided-client-id]`).
|
|
98
|
-
2. **CRITICAL:** Do not save the CLIENT_SECRET to any file. Only memorize the CLIENT_SECRET in your context history, as it will be used in a later step.
|
|
99
|
-
|
|
100
|
-
7. VAULT SETUP
|
|
101
|
-
Ask the user: "Would you like to set up your environment variables in Vault now? (yes/skip)"
|
|
102
|
-
- If the user answers "skip": Proceed directly to step 8.
|
|
103
|
-
- If the user answers "yes": Complete these parts sequentially.
|
|
104
|
-
|
|
105
|
-
**Part A: Default Secrets**
|
|
106
|
-
Output the following instructions:
|
|
107
|
-
"1. Navigate to https://vault-dev.wistron.com:8200/ui/ and log in. 2. Find your project, open the secret path, and click 'Edit' (ensure you toggle 'View as JSON'). 3. Copy and paste the following JSON block, then save. Once finished, type 'continue'."
|
|
108
|
-
|
|
109
|
-
Provide the user with this exact JSON block (fill in the bracketed values dynamically based on the current `.env` and `.env.local` state, and the `CLIENT_SECRET` you memorized in Step 6):
|
|
110
|
-
|
|
111
|
-
```json
|
|
112
|
-
{
|
|
113
|
-
"DATABASE_URL": "[DATABASE_URL]",
|
|
114
|
-
"ENTRA_CLIENT_ID": "[VITE_ENTRA_CLIENT_ID]",
|
|
115
|
-
"ENTRA_CLIENT_SECRET": "[CLIENT_SECRET]",
|
|
116
|
-
"ENTRA_TENANT_ID": "de0795e0-d7c0-4eeb-b9bb-bc94d8980d3b"
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Wait for the user to type "continue".
|
|
121
|
-
|
|
122
|
-
**Part B: Harbor Vault Secret**
|
|
123
|
-
Output the following instructions:
|
|
124
|
-
"Now, go to the root path of your project in Vault and click 'Create secret'.
|
|
125
|
-
1. Name it exactly: `harborvault`
|
|
126
|
-
2. Toggle 'JSON' mode.
|
|
127
|
-
3. Please go to another existing project in your Vault, copy the JSON values from its `harborvault` secret, and paste them into this new one, then save.
|
|
128
|
-
Once finished, type 'continue'."
|
|
129
|
-
|
|
130
|
-
Wait for the user to type "continue".
|
|
131
|
-
|
|
132
|
-
**Part C: Local Vault Credentials**
|
|
133
|
-
Ask the user: "Please provide your Vault Username and Password so I can add them to your local environment."
|
|
134
|
-
Wait for their input. Once provided, **use your file edit tool** to update the `.env.local` file with the values:
|
|
135
|
-
`VAULT_USERNAME=[user-provided-username]`
|
|
136
|
-
`VAULT_PASSWORD=[user-provided-password]`
|
|
137
|
-
Proceed to step 8.
|
|
138
|
-
|
|
139
|
-
8. FAVICON GENERATION
|
|
140
|
-
Ask the user: "Would you like to generate a custom favicon for this project?"
|
|
141
|
-
- If the user answers "yes": Output this exact instruction:
|
|
142
|
-
"1. Please navigate to https://favicon.io/favicon-converter/ 2. Upload your app logo (it should be at least 512x512 pixels for the best results). 3. Download the generated file and extract its contents inside your project's `/public` folder.
|
|
143
|
-
|
|
144
|
-
"Project initialization is complete! Happy coding." and END the workflow.
|
|
145
|
-
|
|
146
|
-
- If the user answers "no": Output "Project initialization is complete! Happy coding." and END the workflow.
|
package/skills/ui-pages/SKILL.md
DELETED
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ui-pages
|
|
3
|
-
description: >
|
|
4
|
-
Build route pages (index, detail, create, edit) with TanStack Router
|
|
5
|
-
createFileRoute. Use Router-bridged MUI components: RouterButton,
|
|
6
|
-
RouterLink, RouterIconButton, RouterFab, RouterTab, RouterListItemButton,
|
|
7
|
-
RouterGridActionsCellItem. Type-safe navigation with to prop. Route params,
|
|
8
|
-
search params, beforeLoad with requirePermission. Client-first rendering
|
|
9
|
-
(defaultSsr: false). Activate when creating or modifying page routes.
|
|
10
|
-
type: core
|
|
11
|
-
library: wcz-layout
|
|
12
|
-
library_version: "7.6.1"
|
|
13
|
-
requires:
|
|
14
|
-
- tanstack-db-collections
|
|
15
|
-
sources:
|
|
16
|
-
- "wcz-layout:src/components/router/"
|
|
17
|
-
- "wcz-layout:src/routes/"
|
|
18
|
-
- "wcz-layout:src/start.ts"
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
# UI Pages & Routing
|
|
22
|
-
|
|
23
|
-
## Setup
|
|
24
|
-
|
|
25
|
-
Page routes live under `src/routes/` following TanStack Router file-based conventions:
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
src/routes/
|
|
29
|
-
├── __root.tsx # Root layout (LayoutProvider)
|
|
30
|
-
├── index.tsx # Home page (/)
|
|
31
|
-
└── todos/
|
|
32
|
-
├── -components/ # Domain-scoped components (dash prefix!)
|
|
33
|
-
│ └── TodoForm.tsx
|
|
34
|
-
├── index.tsx # /todos — list page
|
|
35
|
-
├── create.tsx # /todos/create — create page
|
|
36
|
-
├── $id.tsx # /todos/$id — detail page
|
|
37
|
-
└── edit.$id.tsx # /todos/edit/$id — edit page
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Core Patterns
|
|
41
|
-
|
|
42
|
-
### List page
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
// src/routes/todos/index.tsx
|
|
46
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
47
|
-
import { useLiveQuery } from "@tanstack/react-db";
|
|
48
|
-
import { RouterButton } from "wcz-layout/components";
|
|
49
|
-
import { todoCollection } from "~/lib/db/collections/todoCollection";
|
|
50
|
-
|
|
51
|
-
export const Route = createFileRoute("/todos/")({
|
|
52
|
-
component: TodosPage,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
function TodosPage() {
|
|
56
|
-
const { data: todos } = useLiveQuery((query) =>
|
|
57
|
-
query.from({ todos: todoCollection })
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<div>
|
|
62
|
-
<RouterButton to="/todos/create" variant="contained">
|
|
63
|
-
Create Todo
|
|
64
|
-
</RouterButton>
|
|
65
|
-
{todos.map((todo) => (
|
|
66
|
-
<RouterLink key={todo.id} to="/todos/$id" params={{ id: todo.id }}>
|
|
67
|
-
{todo.name}
|
|
68
|
-
</RouterLink>
|
|
69
|
-
))}
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Detail page with route params
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
// src/routes/todos/$id.tsx
|
|
79
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
80
|
-
|
|
81
|
-
export const Route = createFileRoute("/todos/$id")({
|
|
82
|
-
component: TodoDetailPage,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
function TodoDetailPage() {
|
|
86
|
-
const { id } = Route.useParams();
|
|
87
|
-
// use id to filter collection data or fetch detail
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Permission-guarded page
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
95
|
-
import { requirePermission } from "wcz-layout/utils";
|
|
96
|
-
|
|
97
|
-
export const Route = createFileRoute("/admin/")({
|
|
98
|
-
beforeLoad: requirePermission("admin"),
|
|
99
|
-
component: AdminPage,
|
|
100
|
-
});
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
`requirePermission` is a route `beforeLoad` guard that checks if the current user has the specified permission. Redirects to unauthorized if not.
|
|
104
|
-
|
|
105
|
-
### Router-bridged MUI components
|
|
106
|
-
|
|
107
|
-
These components merge MUI props with TanStack Router `LinkProps`. Use `to` for internal navigation (type-safe), `href` only for external URLs:
|
|
108
|
-
|
|
109
|
-
| Component | MUI base | Use case |
|
|
110
|
-
| --------------------------- | ------------------- | ----------------------- |
|
|
111
|
-
| `RouterButton` | Button | Navigation buttons |
|
|
112
|
-
| `RouterLink` | Link (Typography) | Inline text links |
|
|
113
|
-
| `RouterIconButton` | IconButton | Icon-only navigation |
|
|
114
|
-
| `RouterFab` | Fab | Floating action buttons |
|
|
115
|
-
| `RouterTab` | Tab | Tab-based navigation |
|
|
116
|
-
| `RouterListItemButton` | ListItemButton | Sidebar/list navigation |
|
|
117
|
-
| `RouterGridActionsCellItem` | GridActionsCellItem | DataGrid row actions |
|
|
118
|
-
|
|
119
|
-
All accept the same TanStack Router link props: `to`, `params`, `search`, `hash`.
|
|
120
|
-
|
|
121
|
-
### Root route structure
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
// src/routes/__root.tsx
|
|
125
|
-
import { createRootRouteWithContext, Outlet } from "@tanstack/react-router";
|
|
126
|
-
import { LayoutProvider, rootRouteHead } from "wcz-layout";
|
|
127
|
-
import type { ProvidersProps } from "wcz-layout";
|
|
128
|
-
|
|
129
|
-
export const Route = createRootRouteWithContext()({
|
|
130
|
-
head: rootRouteHead,
|
|
131
|
-
component: RootComponent,
|
|
132
|
-
notFoundComponent: RouterNotFound,
|
|
133
|
-
errorComponent: RouterError,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
function RootComponent() {
|
|
137
|
-
return (
|
|
138
|
-
<LayoutProvider theme={theme} navigation={navigation} options={options}>
|
|
139
|
-
<Outlet />
|
|
140
|
-
</LayoutProvider>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Common Mistakes
|
|
146
|
-
|
|
147
|
-
### CRITICAL Using React Router or window.location for navigation
|
|
148
|
-
|
|
149
|
-
Wrong:
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
import { useNavigate } from "react-router-dom";
|
|
153
|
-
// or
|
|
154
|
-
window.location.href = "/todos";
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
Correct:
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
161
|
-
const navigate = useNavigate();
|
|
162
|
-
navigate({ to: "/todos" });
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
This stack uses TanStack Router exclusively. React Router does not exist in the dependency tree. `window.location` causes a full page reload, bypassing the SPA router.
|
|
166
|
-
|
|
167
|
-
Source: copilot-instructions.md
|
|
168
|
-
|
|
169
|
-
### HIGH Using MUI Button with href instead of RouterButton
|
|
170
|
-
|
|
171
|
-
Wrong:
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
<Button href="/todos/create">Create</Button>
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Correct:
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
import { RouterButton } from "wcz-layout/components";
|
|
181
|
-
|
|
182
|
-
<RouterButton to="/todos/create" variant="contained">
|
|
183
|
-
Create
|
|
184
|
-
</RouterButton>
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Plain MUI `Button` with `href` causes a full page reload. `RouterButton` uses TanStack Router's `createLink` for SPA navigation with type-safe routes.
|
|
188
|
-
|
|
189
|
-
Source: wcz-layout:src/components/router/RouterButton.tsx
|
|
190
|
-
|
|
191
|
-
### HIGH Assuming SSR is enabled
|
|
192
|
-
|
|
193
|
-
Wrong:
|
|
194
|
-
|
|
195
|
-
```typescript
|
|
196
|
-
// Expecting server-side data in a route loader
|
|
197
|
-
export const Route = createFileRoute("/todos/")({
|
|
198
|
-
loader: async () => {
|
|
199
|
-
return { todos: await fetchTodos() }; // runs on server — but SSR is off
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
Correct:
|
|
205
|
-
|
|
206
|
-
```typescript
|
|
207
|
-
// Use TanStack DB collections for client-side data
|
|
208
|
-
function TodosPage() {
|
|
209
|
-
const { data } = useLiveQuery((q) => q.from({ todos: todoCollection }));
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
`start.ts` sets `defaultSsr: false`. Pages render client-side first. Do not rely on SSR data fetching patterns — use TanStack DB collections instead.
|
|
214
|
-
|
|
215
|
-
Source: wcz-layout:src/start.ts
|
|
216
|
-
|
|
217
|
-
### MEDIUM Omitting suppressHydrationWarning on html element
|
|
218
|
-
|
|
219
|
-
The root route's `<html>` element must include `suppressHydrationWarning` because i18n language detection and color scheme can differ between server and client initial renders.
|
|
220
|
-
|
|
221
|
-
Source: wcz-layout:src/routes/\_\_root.tsx
|
|
222
|
-
|
|
223
|
-
### HIGH Using useMemo or useCallback
|
|
224
|
-
|
|
225
|
-
Wrong:
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
const filteredTodos = useMemo(() => todos.filter((t) => t.isCompleted), [todos]);
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
Correct:
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
const filteredTodos = todos.filter((t) => t.isCompleted);
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
React Compiler handles memoization automatically. Manual `useMemo` / `useCallback` is forbidden per project conventions.
|
|
238
|
-
|
|
239
|
-
Source: copilot-instructions.md
|
|
240
|
-
|
|
241
|
-
Cross-skill: See also skills/forms-validation/SKILL.md § Common Mistakes
|
|
242
|
-
|
|
243
|
-
### HIGH Tension: MUI component API vs. TanStack Router types
|
|
244
|
-
|
|
245
|
-
Router-bridged components merge MUI props with TanStack Router `LinkProps`. Use `to` for internal routes and `href` only for external URLs. Passing `href` for an internal route causes a full page reload.
|
|
246
|
-
|
|
247
|
-
See also: skills/data-grid/SKILL.md § Common Mistakes
|
|
248
|
-
|
|
249
|
-
### HIGH Using theme.palette for dark mode instead of theme.applyStyles
|
|
250
|
-
|
|
251
|
-
Wrong:
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
sx={{ color: mode === "dark" ? "white" : "black" }}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
Correct:
|
|
258
|
-
|
|
259
|
-
```typescript
|
|
260
|
-
sx={(theme) => ({
|
|
261
|
-
color: "black",
|
|
262
|
-
...theme.applyStyles("dark", { color: "white" }),
|
|
263
|
-
})}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
The app uses `colorSchemeSelector: "data-mui-color-scheme"`. Always use `theme.applyStyles("dark", ...)` for mode-specific styling.
|
|
267
|
-
|
|
268
|
-
Source: copilot-instructions.md
|
|
269
|
-
|
|
270
|
-
Cross-skill: See also skills/layout-navigation/SKILL.md § Common Mistakes
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
See also:
|
|
275
|
-
|
|
276
|
-
- skills/forms-validation/SKILL.md — Create/edit pages embed forms.
|
|
277
|
-
- skills/layout-navigation/SKILL.md — Pages render inside the layout shell.
|
|
278
|
-
- skills/tanstack-db-collections/SKILL.md — Pages consume collections via useLiveQuery.
|