wcz-layout 7.6.1 → 7.6.2

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.
@@ -0,0 +1,229 @@
1
+ ---
2
+ name: data-grid
3
+ description: >
4
+ MUI X DataGrid Premium with wcz-layout helpers. ChipInputCell for chip
5
+ rendering in cells (getLabel for complex objects). EditableColumnHeader
6
+ for pencil icon headers. RouterGridActionsCellItem for type-safe row
7
+ action navigation. Column definitions with renderCell/renderHeader.
8
+ Feed rows from useLiveQuery collection data. Activate when building
9
+ data tables or grids.
10
+ type: composition
11
+ library: wcz-layout
12
+ library_version: "7.6.1"
13
+ requires:
14
+ - tanstack-db-collections
15
+ - ui-pages
16
+ sources:
17
+ - "wcz-layout:src/components/data-grid/ChipInputCell.tsx"
18
+ - "wcz-layout:src/components/data-grid/EditableColumnHeader.tsx"
19
+ - "wcz-layout:src/components/router/RouterGridActionsCellItem.tsx"
20
+ ---
21
+
22
+ # Data Grid
23
+
24
+ ## Setup
25
+
26
+ Install `@mui/x-data-grid-premium` (peer dependency). Use `DataGridPremium`, not the community `DataGrid`.
27
+
28
+ ## Core Patterns
29
+
30
+ ### Basic data grid with collection data
31
+
32
+ ```typescript
33
+ import { DataGridPremium, type GridColDef } from "@mui/x-data-grid-premium";
34
+ import { useLiveQuery } from "@tanstack/react-db";
35
+ import { todoCollection } from "~/lib/db/collections/todoCollection";
36
+ import type { Todo } from "~/schemas/todo";
37
+
38
+ const columns: GridColDef<Todo>[] = [
39
+ { field: "name", headerName: "Name", flex: 1 },
40
+ { field: "isCompleted", headerName: "Completed", type: "boolean", width: 120 },
41
+ { field: "createdAt", headerName: "Created", type: "dateTime", width: 180 },
42
+ ];
43
+
44
+ function TodoGrid() {
45
+ const { data: todos } = useLiveQuery((query) =>
46
+ query.from({ todos: todoCollection })
47
+ );
48
+
49
+ return (
50
+ <DataGridPremium
51
+ rows={todos}
52
+ columns={columns}
53
+ getRowId={(row) => row.id}
54
+ autoHeight
55
+ />
56
+ );
57
+ }
58
+ ```
59
+
60
+ ### Row actions with RouterGridActionsCellItem
61
+
62
+ ```typescript
63
+ import { RouterGridActionsCellItem } from "wcz-layout/components";
64
+ import EditIcon from "@mui/icons-material/Edit";
65
+ import DeleteIcon from "@mui/icons-material/Delete";
66
+ import type { GridColDef } from "@mui/x-data-grid-premium";
67
+
68
+ const columns: GridColDef<Todo>[] = [
69
+ { field: "name", headerName: "Name", flex: 1 },
70
+ {
71
+ field: "actions",
72
+ type: "actions",
73
+ width: 100,
74
+ getActions: (params) => [
75
+ <RouterGridActionsCellItem
76
+ key="edit"
77
+ icon={<EditIcon />}
78
+ label="Edit"
79
+ to="/todos/edit/$id"
80
+ params={{ id: params.row.id }}
81
+ />,
82
+ <RouterGridActionsCellItem
83
+ key="delete"
84
+ icon={<DeleteIcon />}
85
+ label="Delete"
86
+ onClick={() => handleDelete(params.row)}
87
+ showInMenu
88
+ />,
89
+ ],
90
+ },
91
+ ];
92
+ ```
93
+
94
+ `RouterGridActionsCellItem` wraps MUI's `GridActionsCellItem` with TanStack Router's `createLink` for type-safe SPA navigation.
95
+
96
+ ### ChipInputCell for array/tag columns
97
+
98
+ ```typescript
99
+ import { ChipInputCell } from "wcz-layout/components";
100
+ import type { GridColDef } from "@mui/x-data-grid-premium";
101
+
102
+ const columns: GridColDef<Todo>[] = [
103
+ {
104
+ field: "tags",
105
+ headerName: "Tags",
106
+ flex: 1,
107
+ renderCell: (params) => <ChipInputCell params={params} />,
108
+ },
109
+ ];
110
+ ```
111
+
112
+ For complex objects (not plain strings), provide `getLabel`:
113
+
114
+ ```typescript
115
+ renderCell: (params) => (
116
+ <ChipInputCell
117
+ params={params}
118
+ getLabel={(item) => item.displayName}
119
+ slotProps={{ size: "small", color: "primary" }}
120
+ />
121
+ ),
122
+ ```
123
+
124
+ `ChipInputCell` renders a horizontal scrollable stack of `Chip` components. It handles both scalar and array values automatically.
125
+
126
+ ### EditableColumnHeader
127
+
128
+ ```typescript
129
+ import { EditableColumnHeader } from "wcz-layout/components";
130
+
131
+ const columns: GridColDef<Todo>[] = [
132
+ {
133
+ field: "name",
134
+ headerName: "Name",
135
+ flex: 1,
136
+ editable: true,
137
+ renderHeader: (params) => <EditableColumnHeader {...params} />,
138
+ },
139
+ ];
140
+ ```
141
+
142
+ Renders the column header text with a trailing pencil (Edit) icon to visually indicate editable columns. Purely visual — no interaction.
143
+
144
+ ## Common Mistakes
145
+
146
+ ### HIGH Using DataGrid instead of DataGridPremium
147
+
148
+ Wrong:
149
+
150
+ ```typescript
151
+ import { DataGrid } from "@mui/x-data-grid";
152
+ ```
153
+
154
+ Correct:
155
+
156
+ ```typescript
157
+ import { DataGridPremium } from "@mui/x-data-grid-premium";
158
+ ```
159
+
160
+ The library peer-depends on `@mui/x-data-grid-premium`. Using the community `DataGrid` loses Premium features like column grouping, row grouping, tree data, and aggregation.
161
+
162
+ Source: package.json peerDependencies
163
+
164
+ ### HIGH Using GridActionsCellItem instead of RouterGridActionsCellItem
165
+
166
+ Wrong:
167
+
168
+ ```typescript
169
+ import { GridActionsCellItem } from "@mui/x-data-grid-premium";
170
+
171
+ <GridActionsCellItem
172
+ icon={<EditIcon />}
173
+ label="Edit"
174
+ onClick={() => navigate({ to: `/todos/${id}` })}
175
+ />
176
+ ```
177
+
178
+ Correct:
179
+
180
+ ```typescript
181
+ import { RouterGridActionsCellItem } from "wcz-layout/components";
182
+
183
+ <RouterGridActionsCellItem
184
+ icon={<EditIcon />}
185
+ label="Edit"
186
+ to="/todos/edit/$id"
187
+ params={{ id }}
188
+ />
189
+ ```
190
+
191
+ Plain `GridActionsCellItem` doesn't support TanStack Router navigation. `RouterGridActionsCellItem` wraps it with `createLink` for type-safe routing without manual `onClick` + `navigate`.
192
+
193
+ Source: wcz-layout:src/components/router/RouterGridActionsCellItem.tsx
194
+
195
+ ### MEDIUM Not passing getLabel to ChipInputCell for complex objects
196
+
197
+ Wrong:
198
+
199
+ ```typescript
200
+ // params.value is Array<{ id: string; name: string }>
201
+ <ChipInputCell params={params} />
202
+ // Renders [object Object] chips
203
+ ```
204
+
205
+ Correct:
206
+
207
+ ```typescript
208
+ <ChipInputCell
209
+ params={params}
210
+ getLabel={(item) => item.name}
211
+ />
212
+ ```
213
+
214
+ `ChipInputCell` defaults `getLabel` to identity (value as-is). For objects, you must provide `getLabel` to extract the display string.
215
+
216
+ Source: wcz-layout:src/components/data-grid/ChipInputCell.tsx
217
+
218
+ ### HIGH Tension: MUI component API vs. TanStack Router types
219
+
220
+ `RouterGridActionsCellItem` merges MUI grid action props with TanStack Router `LinkProps`. Use `to` + `params` for internal navigation. Using `onClick` + `navigate` bypasses the type-safe link and router prefetching.
221
+
222
+ See also: skills/ui-pages/SKILL.md § Common Mistakes
223
+
224
+ ---
225
+
226
+ See also:
227
+
228
+ - skills/tanstack-db-collections/SKILL.md — Grid rows come from useLiveQuery results.
229
+ - skills/ui-pages/SKILL.md — Grid pages follow the same routing patterns.
@@ -0,0 +1,182 @@
1
+ ---
2
+ name: database-schema
3
+ description: >
4
+ Design PostgreSQL tables with Drizzle ORM pgTable and derive Zod validation
5
+ schemas via createSelectSchema from drizzle-orm/zod. Covers uuid primary keys,
6
+ column types, Zod refinements (trim, min, max), and type inference. Drizzle
7
+ tables live in src/lib/db/schemas/, Zod schemas in src/schemas/. Activate
8
+ when creating or modifying database tables or validation schemas.
9
+ type: core
10
+ library: wcz-layout
11
+ library_version: "7.6.1"
12
+ sources:
13
+ - "wcz-layout:src/lib/db/schemas/"
14
+ - "wcz-layout:src/schemas/"
15
+ - "drizzle-orm:docs/zod.md"
16
+ ---
17
+
18
+ # Database Schema Design
19
+
20
+ ## Setup
21
+
22
+ Database schema files live in two directories:
23
+
24
+ ```
25
+ src/lib/db/schemas/todo.ts # Drizzle pgTable definition
26
+ src/schemas/todo.ts # Zod schema derived from the table
27
+ ```
28
+
29
+ ## Core Patterns
30
+
31
+ ### Drizzle table definition
32
+
33
+ ```typescript
34
+ // src/lib/db/schemas/todo.ts
35
+ import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
36
+
37
+ export const todoTable = pgTable("todos", {
38
+ id: uuid().primaryKey(),
39
+ name: text().notNull(),
40
+ description: text(),
41
+ isCompleted: boolean().notNull().default(false),
42
+ createdBy: text().notNull(),
43
+ createdAt: timestamp().notNull().defaultNow(),
44
+ updatedAt: timestamp().notNull().defaultNow(),
45
+ });
46
+ ```
47
+
48
+ All tables use `uuid().primaryKey()`. IDs are generated client-side with `uuidv7()`.
49
+
50
+ ### Deriving Zod schema from Drizzle
51
+
52
+ ```typescript
53
+ // src/schemas/todo.ts
54
+ import { createSelectSchema } from "drizzle-orm/zod";
55
+ import { todoTable } from "~/lib/db/schemas/todo";
56
+
57
+ export const TodoSchema = createSelectSchema(todoTable, {
58
+ name: (schema) => schema.trim().min(1),
59
+ description: (schema) => schema.trim().nullish(),
60
+ });
61
+
62
+ export type Todo = typeof TodoSchema._type;
63
+ ```
64
+
65
+ `createSelectSchema` generates a Zod schema matching the table columns. The second argument lets you refine individual fields — add `.trim()`, `.min()`, `.max()`, or replace the schema entirely.
66
+
67
+ ### Insert schema for mutations
68
+
69
+ ```typescript
70
+ // src/schemas/todo.ts
71
+ import { createInsertSchema } from "drizzle-orm/zod";
72
+
73
+ export const TodoInsertSchema = createInsertSchema(todoTable, {
74
+ name: (schema) => schema.trim().min(1),
75
+ });
76
+ ```
77
+
78
+ Use `createInsertSchema` when you need a schema for insert operations that omits fields with defaults (like `id`, `createdAt`).
79
+
80
+ ### Type inference
81
+
82
+ ```typescript
83
+ import type { InferSelectModel, InferInsertModel } from "drizzle-orm";
84
+ import { todoTable } from "~/lib/db/schemas/todo";
85
+
86
+ type Todo = InferSelectModel<typeof todoTable>;
87
+ type NewTodo = InferInsertModel<typeof todoTable>;
88
+ ```
89
+
90
+ Prefer the Zod `_type` for runtime-validated types. Use Drizzle inference types when you need the raw DB shape without validation.
91
+
92
+ ## Common Mistakes
93
+
94
+ ### CRITICAL Writing Zod schemas manually instead of deriving from Drizzle
95
+
96
+ Wrong:
97
+
98
+ ```typescript
99
+ import { z } from "zod";
100
+
101
+ export const TodoSchema = z.object({
102
+ id: z.string().uuid(),
103
+ name: z.string().trim().min(1),
104
+ isCompleted: z.boolean(),
105
+ });
106
+ ```
107
+
108
+ Correct:
109
+
110
+ ```typescript
111
+ import { createSelectSchema } from "drizzle-orm/zod";
112
+ import { todoTable } from "~/lib/db/schemas/todo";
113
+
114
+ export const TodoSchema = createSelectSchema(todoTable, {
115
+ name: (schema) => schema.trim().min(1),
116
+ });
117
+ ```
118
+
119
+ Manual Zod schemas drift from the database when columns are added or renamed. `createSelectSchema` keeps them in sync automatically.
120
+
121
+ Source: maintainer interview
122
+
123
+ ### HIGH Placing schema files in wrong directory
124
+
125
+ Wrong:
126
+
127
+ ```
128
+ src/lib/db/schemas/todo.ts # Contains both pgTable AND Zod schema
129
+ ```
130
+
131
+ Correct:
132
+
133
+ ```
134
+ src/lib/db/schemas/todo.ts # Only pgTable definition
135
+ src/schemas/todo.ts # Zod schema derived from the table
136
+ ```
137
+
138
+ Drizzle table definitions and Zod validation schemas live in separate directories. Mixing them in one file violates the project structure convention.
139
+
140
+ Source: consumer project structure
141
+
142
+ ### HIGH Forgetting uuid primary key convention
143
+
144
+ Wrong:
145
+
146
+ ```typescript
147
+ import { serial, pgTable, text } from "drizzle-orm/pg-core";
148
+
149
+ export const todoTable = pgTable("todos", {
150
+ id: serial().primaryKey(),
151
+ name: text().notNull(),
152
+ });
153
+ ```
154
+
155
+ Correct:
156
+
157
+ ```typescript
158
+ import { uuid, pgTable, text } from "drizzle-orm/pg-core";
159
+
160
+ export const todoTable = pgTable("todos", {
161
+ id: uuid().primaryKey(),
162
+ name: text().notNull(),
163
+ });
164
+ ```
165
+
166
+ All tables use `uuid().primaryKey()`. IDs are generated client-side with `uuidv7()` for optimistic inserts through TanStack DB collections.
167
+
168
+ Source: consumer project example
169
+
170
+ ### HIGH Tension: Type safety vs. rapid prototyping
171
+
172
+ Deriving Zod schemas from Drizzle tables and wiring them through forms requires more setup than quick `useState` + manual validation. Always use the enforced pattern — `createSelectSchema` + `useLayoutForm` — even for simple forms.
173
+
174
+ See also: skills/forms-validation/SKILL.md § Core Patterns
175
+
176
+ ---
177
+
178
+ See also:
179
+
180
+ - skills/api-routes/SKILL.md — Schemas feed into validationMiddleware(Schema).
181
+ - skills/tanstack-db-collections/SKILL.md — Same Zod schema used as collection schema option.
182
+ - skills/forms-validation/SKILL.md — Zod schemas used as form validators.
@@ -0,0 +1,241 @@
1
+ ---
2
+ name: dialogs-notifications
3
+ description: >
4
+ Use useDialogs for alert/confirm/custom dialogs and useNotification for
5
+ snackbar messages. Promise-based dialog API with DialogProps type for
6
+ custom dialogs. Notification severity (success, error, warning, info)
7
+ with configurable autoHideDuration. Activate when showing confirmations,
8
+ alerts, custom dialogs, or toast notifications.
9
+ type: core
10
+ library: wcz-layout
11
+ library_version: "7.6.1"
12
+ sources:
13
+ - "wcz-layout:src/hooks/DialogsHooks.tsx"
14
+ - "wcz-layout:src/providers/DialogsProvider.tsx"
15
+ - "wcz-layout:src/contexts/NotificationContext.tsx"
16
+ ---
17
+
18
+ # Dialogs & Notifications
19
+
20
+ ## Setup
21
+
22
+ Both hooks are available inside the `LayoutProvider` tree (which includes `DialogsProvider` and `NotificationProvider`):
23
+
24
+ ```typescript
25
+ import { useDialogs, useNotification } from "wcz-layout/hooks";
26
+ ```
27
+
28
+ ## Core Patterns
29
+
30
+ ### Confirmation dialog before deletion
31
+
32
+ ```typescript
33
+ import { useDialogs, useNotification } from "wcz-layout/hooks";
34
+ import { todoCollection } from "~/lib/db/collections/todoCollection";
35
+ import type { Todo } from "~/schemas/todo";
36
+
37
+ function DeleteButton({ todo }: { todo: Todo }) {
38
+ const { confirm } = useDialogs();
39
+ const { notify } = useNotification();
40
+
41
+ const handleDelete = async () => {
42
+ const confirmed = await confirm("Delete this item?");
43
+ if (confirmed) {
44
+ todoCollection.delete(todo);
45
+ notify("Item deleted", { severity: "success" });
46
+ }
47
+ };
48
+
49
+ return <Button onClick={handleDelete}>Delete</Button>;
50
+ }
51
+ ```
52
+
53
+ ### Alert dialog
54
+
55
+ ```typescript
56
+ const { alert } = useDialogs();
57
+
58
+ await alert("Operation completed successfully", { title: "Success" });
59
+ // Resolves when user clicks OK
60
+ ```
61
+
62
+ ### Custom dialog
63
+
64
+ Define the dialog component with `DialogProps`:
65
+
66
+ ```typescript
67
+ import type { DialogProps } from "wcz-layout/hooks";
68
+ import {
69
+ Dialog,
70
+ DialogTitle,
71
+ DialogContent,
72
+ DialogActions,
73
+ Button,
74
+ TextField,
75
+ } from "@mui/material";
76
+ import { useState } from "react";
77
+
78
+ interface NotePayload {
79
+ title: string;
80
+ }
81
+
82
+ function NoteDialog({ payload, open, onClose }: DialogProps<NotePayload, string>) {
83
+ const [note, setNote] = useState("");
84
+
85
+ return (
86
+ <Dialog open={open} onClose={() => onClose("")} maxWidth="sm" fullWidth>
87
+ <DialogTitle>{payload.title}</DialogTitle>
88
+ <DialogContent>
89
+ <TextField
90
+ autoFocus
91
+ fullWidth
92
+ label="Note"
93
+ value={note}
94
+ onChange={(e) => setNote(e.target.value)}
95
+ />
96
+ </DialogContent>
97
+ <DialogActions>
98
+ <Button onClick={() => onClose("")}>Cancel</Button>
99
+ <Button onClick={() => onClose(note)} variant="contained">
100
+ Save
101
+ </Button>
102
+ </DialogActions>
103
+ </Dialog>
104
+ );
105
+ }
106
+ ```
107
+
108
+ Open the custom dialog and await its result:
109
+
110
+ ```typescript
111
+ const { open } = useDialogs();
112
+
113
+ const note = await open(NoteDialog, { title: "Add a note" });
114
+ if (note) {
115
+ // user entered a note
116
+ }
117
+ ```
118
+
119
+ ### Dialog API reference
120
+
121
+ ```typescript
122
+ interface DialogHook {
123
+ alert(message: string, options?: { title?: string }): Promise<void>;
124
+ confirm(message: string, options?: { title?: string; cancelText?: string }): Promise<boolean>;
125
+ open<TPayload, TResult>(
126
+ Component: ComponentType<DialogProps<TPayload, TResult>>,
127
+ payload?: TPayload,
128
+ options?: DialogOptions,
129
+ ): Promise<TResult>;
130
+ close<TResult>(dialogPromise: Promise<TResult>, result: TResult): Promise<TResult>;
131
+ }
132
+ ```
133
+
134
+ ### Notifications
135
+
136
+ ```typescript
137
+ const { notify } = useNotification();
138
+
139
+ notify("Changes saved", { severity: "success" });
140
+ notify("Something went wrong", { severity: "error" });
141
+ notify("Check your input", { severity: "warning" });
142
+ notify("New version available", { severity: "info" });
143
+
144
+ // Custom auto-hide duration (default: 5000ms)
145
+ notify("Processing...", { severity: "info", autoHideDuration: 10000 });
146
+ ```
147
+
148
+ ### After form submission
149
+
150
+ ```typescript
151
+ const form = useLayoutForm({
152
+ defaultValues: { name: "" } as Todo,
153
+ validators: { onChange: TodoSchema },
154
+ onSubmit: ({ value }) => {
155
+ todoCollection.insert(value);
156
+ notify("Todo created", { severity: "success" });
157
+ },
158
+ });
159
+ ```
160
+
161
+ ## Common Mistakes
162
+
163
+ ### HIGH Using window.confirm instead of useDialogs
164
+
165
+ Wrong:
166
+
167
+ ```typescript
168
+ if (window.confirm("Delete this item?")) {
169
+ todoCollection.delete(todo);
170
+ }
171
+ ```
172
+
173
+ Correct:
174
+
175
+ ```typescript
176
+ const { confirm } = useDialogs();
177
+
178
+ const confirmed = await confirm("Delete this item?");
179
+ if (confirmed) {
180
+ todoCollection.delete(todo);
181
+ }
182
+ ```
183
+
184
+ `window.confirm` is a blocking browser dialog that ignores MUI theming and i18n. `useDialogs().confirm` provides themed, async/await MUI dialogs.
185
+
186
+ Source: wcz-layout:src/hooks/DialogsHooks.tsx
187
+
188
+ ### CRITICAL Custom dialog not calling onClose
189
+
190
+ Wrong:
191
+
192
+ ```typescript
193
+ function MyDialog({ payload, open }: DialogProps<string>) {
194
+ return (
195
+ <Dialog open={open}>
196
+ <Button onClick={() => {}}>Done</Button>
197
+ </Dialog>
198
+ );
199
+ }
200
+ ```
201
+
202
+ Correct:
203
+
204
+ ```typescript
205
+ function MyDialog({ payload, open, onClose }: DialogProps<string>) {
206
+ return (
207
+ <Dialog open={open} onClose={() => onClose()}>
208
+ <Button onClick={() => onClose()}>Done</Button>
209
+ </Dialog>
210
+ );
211
+ }
212
+ ```
213
+
214
+ `open()` returns a `Promise` that resolves when `onClose(result)` is called. Forgetting `onClose` leaves the promise hanging permanently and the dialog stuck open. Always wire both the `Dialog` `onClose` prop and button click handlers.
215
+
216
+ Source: wcz-layout:src/providers/DialogsProvider.tsx
217
+
218
+ ### HIGH Using useDialogs outside LayoutProvider tree
219
+
220
+ Wrong:
221
+
222
+ ```typescript
223
+ // Component rendered outside LayoutProvider
224
+ function StandaloneComponent() {
225
+ const { alert } = useDialogs(); // context has unsafe default — crashes on call
226
+ }
227
+ ```
228
+
229
+ Correct:
230
+
231
+ Ensure all components using `useDialogs` or `useNotification` are rendered inside the `LayoutProvider` component tree (which wraps `DialogsProvider` and `NotificationProvider`).
232
+
233
+ `DialogsContext` uses an unsafe cast default value. Accessing the context outside the provider doesn't throw on `useContext`, but crashes when `alert`, `confirm`, or `open` are invoked.
234
+
235
+ Source: wcz-layout:src/contexts/DialogsContext.ts
236
+
237
+ ---
238
+
239
+ See also:
240
+
241
+ - skills/forms-validation/SKILL.md — Form submissions typically show notifications or confirmations.