@dextinity/agent-features 2.0.0-canary-20260729062014

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.
Files changed (39) hide show
  1. package/LICENSE +24 -0
  2. package/package.json +19 -0
  3. package/rules/coding-guidelines/api-nestjs.instructions.md +107 -0
  4. package/rules/coding-guidelines/cdn.instructions.md +24 -0
  5. package/rules/coding-guidelines/general.instructions.md +30 -0
  6. package/rules/coding-guidelines/git.instructions.md +37 -0
  7. package/rules/coding-guidelines/kubernetes.instructions.md +59 -0
  8. package/rules/coding-guidelines/libraries.instructions.md +34 -0
  9. package/rules/coding-guidelines/naming.instructions.md +39 -0
  10. package/rules/coding-guidelines/postgresql.instructions.md +40 -0
  11. package/rules/coding-guidelines/react.instructions.md +102 -0
  12. package/rules/coding-guidelines/security.instructions.md +44 -0
  13. package/rules/coding-guidelines/styling.instructions.md +50 -0
  14. package/rules/coding-guidelines/typescript.instructions.md +50 -0
  15. package/skills/.gitkeep +0 -0
  16. package/skills/comet-admin-ui/SKILL.md +492 -0
  17. package/skills/comet-block/SKILL.md +252 -0
  18. package/skills/comet-block/references/admin-patterns.md +192 -0
  19. package/skills/comet-block/references/api-patterns.md +183 -0
  20. package/skills/comet-block/references/block-loader.md +368 -0
  21. package/skills/comet-block/references/block-types.md +210 -0
  22. package/skills/comet-block/references/custom-block-field.md +266 -0
  23. package/skills/comet-block/references/fixtures.md +436 -0
  24. package/skills/comet-block/references/image.md +341 -0
  25. package/skills/comet-block/references/migration.md +597 -0
  26. package/skills/comet-block/references/registration.md +167 -0
  27. package/skills/comet-block/references/response-summary.md +102 -0
  28. package/skills/comet-block/references/rich-text.md +309 -0
  29. package/skills/comet-block/references/select.md +176 -0
  30. package/skills/comet-block/references/site-patterns.md +202 -0
  31. package/skills/comet-core-admin-component-authoring/SKILL.md +92 -0
  32. package/skills/comet-mail-react/SKILL.md +621 -0
  33. package/skills/comet-mail-react/references/components-and-theme.md +431 -0
  34. package/skills/comet-mail-react/references/layout-patterns.md +315 -0
  35. package/skills/comet-mail-react/references/styling-and-customization.md +306 -0
  36. package/skills/comet-major-migration/SKILL.md +161 -0
  37. package/skills/comet-major-migration/references/migration-smoke-test.md +196 -0
  38. package/skills/comet-minor-update/SKILL.md +191 -0
  39. package/skills/dev-pm/SKILL.md +100 -0
@@ -0,0 +1,50 @@
1
+ ---
2
+ description: TypeScript style — options objects, named exports, async/await, enums
3
+ applyTo: "**/*.ts,**/*.tsx"
4
+ paths:
5
+ - "**/*.{ts,tsx}"
6
+ globs:
7
+ - "**/*.{ts,tsx}"
8
+ alwaysApply: false
9
+ ---
10
+
11
+ # TypeScript Rules
12
+
13
+ ## Function signatures
14
+
15
+ - If a function takes **more than 2** parameters, use a single options object. This makes call sites self-documenting and avoids argument-order mistakes.
16
+
17
+ ```ts
18
+ // Bad
19
+ getSortedJobs("createdAt", true, 20);
20
+
21
+ // Good
22
+ getSortedJobs({ orderBy: "createdAt", includeSoftDeleted: true, limit: 20 });
23
+ ```
24
+
25
+ - Use default argument values (`function f(name = "X")`) instead of `name || "X"` or conditional fallbacks inside the body.
26
+ - Prefer `param?: T` over `param: T | undefined` — the optional form does not require callers to explicitly pass `undefined`.
27
+
28
+ ## Imports / exports
29
+
30
+ - Use **named exports only**. Default exports are allowed only when technically required (e.g. a framework demands it).
31
+ - Use relative imports only for **sibling or child** files within the same module. Everything else uses the `@src/…` alias.
32
+
33
+ ## Async & iteration
34
+
35
+ - Prefer `async/await` over raw callbacks or `.then()` chains.
36
+ - Prefer `for…of` over `Array.prototype.forEach` — it supports `await`, `break`, `continue`, and all iterables.
37
+
38
+ ## Enums
39
+
40
+ - Enum **keys** and **values** use `camelCase`, and keys must equal their values.
41
+
42
+ ```ts
43
+ enum Direction {
44
+ north = "north",
45
+ northEast = "northEast",
46
+ // …
47
+ }
48
+ ```
49
+
50
+ - GraphQL-exposed enums follow a different rule — see [api-nestjs.instructions.md](api-nestjs.instructions.md#graphql-enums).
File without changes
@@ -0,0 +1,492 @@
1
+ ---
2
+ name: comet-admin-ui
3
+ description: Building or editing admin UI in a project that uses @dextinity/admin and its sibling packages — pages, dashboards, dialogs, widgets, layouts, or component styling. Use even for small UI changes, to build with Comet's theme, components, and helpers instead of custom sx/styled CSS, hard-coded values, or Box layouts.
4
+ ---
5
+
6
+ # Building admin UIs with @dextinity/admin
7
+
8
+ `@dextinity/admin` and its sibling packages ship a design system: a theme (spacing,
9
+ colors, shadows, typography, breakpoints) and a library of ready-made components. For
10
+ internationalization, Comet recommends `react-intl` (the default) to translate text, numbers, and
11
+ dates. The components and types are available in the consuming project through the installed
12
+ packages — import them directly (e.g. `import { Button, MainContent } from "@dextinity/admin"`).
13
+
14
+ Build admin UI by composing what the design system already provides. Add custom styling only
15
+ after the system genuinely can't express what you need.
16
+
17
+ ## Core principle
18
+
19
+ **Prefer Comet's theme values, components, and helpers over custom styling.** Three reasons:
20
+
21
+ 1. **Reviewability.** When styling lives in the theme and in components, the markup stays
22
+ declarative and diffs stay small. A component that mixes `sx`, inline `style`, and `styled()`
23
+ is hard to read and hard to review — the layout, the styling, and the logic blur together.
24
+ 2. **Automatic upgrades.** A project's visual design is often built against a _future_ version of
25
+ the design system, so it won't fully match what the currently installed components and theme
26
+ produce. That gap is expected — it is not a reason to add custom styling to force the match.
27
+ Use the current Comet components and tokens as they are; a later library upgrade closes the gap
28
+ on its own, with no hand-written CSS to find and rework.
29
+ 3. **Consistency.** Every screen built from the same components and tokens looks and behaves the
30
+ same way.
31
+
32
+ This holds **even when a project's design deliberately differs** from the current library
33
+ defaults: prefer the Comet component or token, and apply that project-specific difference by
34
+ configuring the theme — not by re-styling individual components. Add custom styling only when
35
+ explicitly instructed, or when no component, prop, or token can produce the result.
36
+
37
+ ## Decision framework
38
+
39
+ Before writing any styling or markup, work down this list and stop at the first step that applies:
40
+
41
+ 1. **Is there a component for this?** Use it rather than assembling the same thing from
42
+ `Box` + CSS (page structure, cards, toolbars, dialogs, alerts, buttons, …).
43
+ 2. **Is there a prop for this?** Props apply the correct theme values with no CSS — e.g.
44
+ `elevation` / `square` on `Paper` and `Card`, `variant` on `Button` and `Typography`,
45
+ `spacing` on `Stack` and `Grid`.
46
+ 3. **Is there a theme value for this?** Read spacing, colors, and shadows from the theme
47
+ (`theme.spacing(n)`, `theme.palette.*`, `theme.shadows[n]`) instead of hard-coding pixels,
48
+ hex colors, or shadow strings.
49
+ 4. **Is there a helper for this?** User-facing text, numbers, and dates go through the i18n
50
+ helpers (`FormattedMessage`, `FormattedNumber`, `FormattedDate`), never hard-coded.
51
+ 5. **Only then, custom-style** — using `styled()` (not `sx` or inline `style`) and reading
52
+ values from the theme.
53
+
54
+ ## Styling and theme
55
+
56
+ ### Custom styling: `styled()`, not `sx` or inline `style`
57
+
58
+ When you do need custom styling, write it with `styled()` from `@mui/material/styles` and give the
59
+ result a name that says what it is. Styling in `sx` props or inline `style` mixes the look into the
60
+ markup, so layout, styling, and logic blur together and the diff is harder to follow. A named
61
+ styled component keeps the markup declarative and the styling in one place.
62
+
63
+ ```tsx
64
+ // Avoid — sx and inline style mixed into the markup
65
+ <Box sx={{ padding: 2, backgroundColor: "#fff", borderRadius: 1 }} style={{ marginTop: 16 }}>
66
+ {children}
67
+ </Box>;
68
+
69
+ // Prefer — a named styled component, styling separated from markup
70
+ const Panel = styled("div")`
71
+ padding: ${({ theme }) => theme.spacing(2)};
72
+ background-color: ${({ theme }) => theme.palette.background.paper};
73
+ `;
74
+
75
+ <Panel>{children}</Panel>;
76
+ ```
77
+
78
+ ### Spacing and color: theme tokens, not hard-coded values
79
+
80
+ Read spacing and color from the theme instead of typing pixels and hex codes. The theme is the
81
+ single place those values are defined, so reading from it keeps every screen consistent and lets a
82
+ theme change reach all of them at once. Comet's spacing base is `5px` — `theme.spacing(1)` is `5px`,
83
+ `theme.spacing(2)` is `10px` — and it takes up to four arguments for top, right, bottom, and left.
84
+
85
+ ```tsx
86
+ // Avoid — hard-coded pixels and colors
87
+ const Header = styled("header")`
88
+ padding: 16px 24px;
89
+ color: #1a1a1a;
90
+ border-bottom: 1px solid #e0e0e0;
91
+ `;
92
+
93
+ // Prefer — spacing and palette tokens from the theme
94
+ const Header = styled("header")`
95
+ padding: ${({ theme }) => theme.spacing(2, 3)};
96
+ color: ${({ theme }) => theme.palette.text.primary};
97
+ border-bottom: 1px solid ${({ theme }) => theme.palette.divider};
98
+ `;
99
+ ```
100
+
101
+ Use the palette tokens — `primary`, `secondary`, `error`, `warning`, `info`, `success`,
102
+ `grey[50…900]`, `divider`, `text`, `background`, `action` — rather than naming raw colors.
103
+
104
+ ### Elevation and shape: `elevation` and `square`, not manual CSS
105
+
106
+ Shadows and corner radius come from props on `Paper` and `Card`, not hand-written CSS. Comet defines
107
+ four shadow elevations (1–4); higher values are `none`. The `elevation` prop selects one, and the
108
+ `square` prop toggles the rounded corner. Read `theme.shadows[n]` directly only inside a `styled()`
109
+ component that cannot be a `Paper` or `Card`.
110
+
111
+ ```tsx
112
+ // Avoid — manual shadow and radius on a plain element
113
+ <div style={{ boxShadow: "0 0 8px rgba(0,0,0,0.1)", borderRadius: 4 }}>{children}</div>
114
+
115
+ // Prefer — a Paper carrying the theme's elevation and shape
116
+ <Paper elevation={2}>{children}</Paper>
117
+ ```
118
+
119
+ ### Typography: `<Typography variant>`, not manual font CSS
120
+
121
+ Render text through `<Typography>` with a variant rather than setting font size, weight, and line
122
+ height by hand. The variant carries the type scale and its responsive steps, so headings and body
123
+ text stay in proportion across breakpoints. Available variants: `h1`–`h6`, `body1`, `body2`,
124
+ `subtitle1`, `subtitle2`, `caption`, `overline`, `list`, `listItem`, `button`.
125
+
126
+ ```tsx
127
+ // Avoid — font properties set by hand
128
+ const Title = styled("h2")`
129
+ font-size: 20px;
130
+ font-weight: 600;
131
+ line-height: 26px;
132
+ `;
133
+
134
+ // Prefer — a Typography variant from the type scale
135
+ <Typography variant="h4">{title}</Typography>;
136
+ ```
137
+
138
+ ## Organizing styled components
139
+
140
+ By default, define a component's styled parts at the bottom of its own file, below the component
141
+ that uses them:
142
+
143
+ ```
144
+ imports → types → component → styled components
145
+ ```
146
+
147
+ When a file grows hard to read, refactor the component itself first — split it into smaller
148
+ components and compose them, each keeping its own styled parts at the bottom. Move styles to a
149
+ separate `*.sc.ts` sibling only when you are asked to, or when the styles grow but the component
150
+ cannot be split logically. A `*.sc.ts` file is private to its equally-named component
151
+ (`FooButton.sc.ts` belongs to `FooButton.tsx`). Don't import one component's `*.sc.ts` from another:
152
+ that couples them through styling neither owns. When styling is shared, give it a single owner — a
153
+ reusable component (below).
154
+
155
+ When the same styled component is used by several components, it is no longer a styled part of any
156
+ one of them. Promote it to its own reusable component: one export per file, named exactly as that
157
+ export so it is easy to find, e.g. `SpecialButton.ts` exporting
158
+ `export const SpecialButton = styled(Button)`. Group several small related ones into a single
159
+ generically-named file only when explicitly instructed.
160
+
161
+ ## Internationalization
162
+
163
+ Comet recommends `react-intl` (the default). When a project uses it, its user-facing text,
164
+ numbers, and dates go through the `react-intl` helpers instead of being hard-coded.
165
+
166
+ ### Text: `<FormattedMessage>` and `useIntl`, not literals
167
+
168
+ Use `<FormattedMessage>` wherever a ReactNode fits. String attributes (`alt`, `placeholder`,
169
+ `title`, `aria-*`) take a string, not a ReactNode, so translate those with
170
+ `useIntl().formatMessage()`.
171
+
172
+ ```tsx
173
+ // Avoid — hard-coded user-facing text
174
+ <Button>Save</Button>;
175
+ <img src={src} alt="Preview" />;
176
+
177
+ // Prefer — translate through react-intl (formatMessage for string attributes)
178
+ import { FormattedMessage, useIntl } from "react-intl";
179
+
180
+ <Button>
181
+ <FormattedMessage id="product.save" defaultMessage="Save" />
182
+ </Button>;
183
+
184
+ const intl = useIntl();
185
+ <img src={src} alt={intl.formatMessage({ id: "product.previewAlt", defaultMessage: "Preview" })} />;
186
+ ```
187
+
188
+ Interpolate runtime values with `values` rather than concatenating strings, and pluralize with ICU
189
+ syntax rather than by hand:
190
+
191
+ ```tsx
192
+ <FormattedMessage id="product.greeting" defaultMessage="Welcome, {name}" values={{ name }} />;
193
+
194
+ // # is the formatted count
195
+ <FormattedMessage id="product.cartCount" defaultMessage="{count, plural, one {# item in cart} other {# items in cart}}" values={{ count }} />;
196
+ ```
197
+
198
+ ### Numbers: `<FormattedNumber>` / `intl.formatNumber()`
199
+
200
+ Format plain numbers, currency, and percentages through react-intl so grouping, decimals, and
201
+ symbols follow the active locale rather than a hand-written format.
202
+
203
+ ```tsx
204
+ // Avoid — raw or hand-formatted numbers (no locale grouping, decimals, or symbol)
205
+ <span>{count}</span>;
206
+ <span>{`${price.toFixed(2)} €`}</span>;
207
+ <span>{`${Math.round(ratio * 100)}%`}</span>;
208
+
209
+ // Prefer — locale-aware grouping and decimals, currency, and percent
210
+ <FormattedNumber value={count} />;
211
+ <FormattedNumber value={price} style="currency" currency="EUR" />;
212
+ <FormattedNumber value={ratio} style="percent" />;
213
+ ```
214
+
215
+ ### Dates and times: `<FormattedDate>` / `<FormattedTime>`
216
+
217
+ Render dates and times through react-intl so they follow the active locale rather than a
218
+ hand-built format.
219
+
220
+ ```tsx
221
+ // Avoid — hand-built date and time strings
222
+ <span>{date.toLocaleDateString("en-US")}</span>;
223
+ <span>{date.toLocaleTimeString("en-US")}</span>;
224
+
225
+ // Prefer — locale-aware formatting
226
+ <FormattedDate value={date} year="numeric" month="long" day="numeric" />;
227
+ <FormattedTime value={date} />;
228
+ ```
229
+
230
+ ## Layout
231
+
232
+ ### Arranging elements: `Stack` and `Grid`, not `Box` with margins
233
+
234
+ To arrange children and the space between them, use MUI's `Stack` (one-dimensional flow with a
235
+ `spacing` prop) and `Grid` (responsive columns with `spacing` and `size`), imported from
236
+ `@mui/material`. Both apply spacing from the theme through props, so you never hand-write the
237
+ gaps. Use `Box` with manual `margin` only when neither fits.
238
+
239
+ Pure layout — arranging children and the gaps between them — is fine inline through these props
240
+ and needs no `styled()`. Anything beyond that (padding inside an element, background, borders,
241
+ and other visual styling) goes through the theme and `styled()`, as in the styling section
242
+ above.
243
+
244
+ ```tsx
245
+ // Avoid — Box with hand-written margins between children
246
+ <Box>
247
+ <Widget />
248
+ <Box sx={{ marginTop: 16 }}>
249
+ <Widget />
250
+ </Box>
251
+ <Box sx={{ marginTop: 16 }}>
252
+ <Widget />
253
+ </Box>
254
+ </Box>;
255
+
256
+ // Prefer — Stack with spacing from the theme
257
+ <Stack spacing={4}>
258
+ <Widget />
259
+ <Widget />
260
+ <Widget />
261
+ </Stack>;
262
+ ```
263
+
264
+ ```tsx
265
+ // Avoid — manual flex and width math for a responsive two-column layout
266
+ <Box sx={{ display: "flex", flexWrap: "wrap" }}>
267
+ <Box sx={{ width: "50%" }}>
268
+ <Widget />
269
+ </Box>
270
+ <Box sx={{ width: "50%" }}>
271
+ <Widget />
272
+ </Box>
273
+ </Box>;
274
+
275
+ // Prefer — Grid with responsive size and spacing from the theme
276
+ <Grid container spacing={4}>
277
+ <Grid size={{ xs: 12, md: 6 }}>
278
+ <Widget />
279
+ </Grid>
280
+ <Grid size={{ xs: 12, md: 6 }}>
281
+ <Widget />
282
+ </Grid>
283
+ </Grid>;
284
+ ```
285
+
286
+ For responsive behaviour, pass a per-breakpoint object to these props (`size` on `Grid`,
287
+ `direction` on `Stack`); inside a `styled()` component, use `theme.breakpoints` for media
288
+ queries.
289
+
290
+ Don't use `Grid` or `Stack` to lay out form fields: Comet stacks them vertically at full width,
291
+ grouped with `FieldSet` or `FormSection`.
292
+
293
+ `sx` is fine for an occasional layout property that no `Stack` or `Grid` prop covers, such as
294
+ `flexGrow`. It is not for visual styling — that goes through `styled()`.
295
+
296
+ ### Page structure: `MainContent`, `Toolbar`, and their parts
297
+
298
+ Wrap a page's body in `MainContent` rather than a hand-padded `Box` — it applies the standard
299
+ page padding and can fill the height with `fullHeight`. Build the action bar from `Toolbar` and
300
+ its parts instead of assembling one from a flex row:
301
+
302
+ - `ToolbarTitleItem` holds the page title, `ToolbarActions` the action buttons; `ToolbarItem` is
303
+ the generic slot for anything else.
304
+ - `FillSpace` is a flexbox spacer that fills the free space, moving the elements after it to the
305
+ end.
306
+
307
+ ```tsx
308
+ // Avoid — hand-built toolbar and padded container
309
+ <Box sx={{ display: "flex", padding: 16 }}>
310
+ <Typography variant="h4">{title}</Typography>
311
+ <Box sx={{ marginLeft: "auto" }}>
312
+ <Button>{addLabel}</Button>
313
+ </Box>
314
+ </Box>;
315
+
316
+ // Prefer — Toolbar parts and MainContent
317
+ <Toolbar>
318
+ <ToolbarTitleItem>{title}</ToolbarTitleItem>
319
+ <FillSpace />
320
+ <ToolbarActions>
321
+ <Button>{addLabel}</Button>
322
+ </ToolbarActions>
323
+ </Toolbar>;
324
+ <MainContent>{children}</MainContent>;
325
+ ```
326
+
327
+ When a page is rendered inside a Comet navigation `Stack` (nested master–detail views), use the
328
+ `StackMainContent` and `StackToolbar` variants instead: they render only for the active stack
329
+ level, so nested pages don't show duplicate toolbars.
330
+
331
+ ### Full-height content: `fullHeight` and `FullHeightContent`
332
+
333
+ Content that should fill the viewport and scroll inside itself — most often a `DataGrid` — needs
334
+ a height-bounded parent, or it grows the whole page instead of scrolling. Set that height through
335
+ the page structure, not a hand-written `height` that has to track the header and toolbar offset:
336
+
337
+ - When the grid is the page's direct content, add `fullHeight` to `MainContent` or
338
+ `StackMainContent`.
339
+ - When the grid is nested inside `RouterTabs` or other content rather than placed directly in
340
+ `MainContent`, wrap it in `FullHeightContent`, which bounds the height at that level.
341
+ - When the grid holds few rows, give the `DataGrid` the `autoHeight` prop instead and skip
342
+ `fullHeight`.
343
+
344
+ ```tsx
345
+ // Avoid — a hand-set height that has to track the header and toolbar offset
346
+ <MainContent>
347
+ <Box sx={{ height: "calc(100vh - 200px)" }}>
348
+ <DataGrid />
349
+ </Box>
350
+ </MainContent>;
351
+
352
+ // Prefer — fullHeight for a grid that is the page's direct content
353
+ <StackMainContent fullHeight>
354
+ <DataGrid />
355
+ </StackMainContent>;
356
+
357
+ // Prefer — FullHeightContent for a grid nested inside tabs
358
+ <MainContent>
359
+ <RouterTabs>
360
+ <RouterTab path="" label={label}>
361
+ <FullHeightContent>
362
+ <DataGrid />
363
+ </FullHeightContent>
364
+ </RouterTab>
365
+ </RouterTabs>
366
+ </MainContent>;
367
+ ```
368
+
369
+ ## Components
370
+
371
+ ### Containers and widgets: `FieldSet`, `FormSection`, and themed `Card`, not `Box`
372
+
373
+ To group related content, use a container component instead of a `Box` with hand-set padding,
374
+ borders, and a title. `FieldSet` (from `@dextinity/admin`) is a collapsible titled panel — wrap a page
375
+ form's fields in it. Inside a dialog or sidebar, group fields with `FormSection` instead, a lighter
376
+ titled section with a divider. For a dashboard widget, if the project uses `@dextinity/cms-admin` (most
377
+ do), use its ready-made `DashboardWidgetRoot` rather than building one by hand; when you compose a
378
+ container yourself, build it from MUI's `Card` (with `CardHeader` and `CardContent`) or `Paper`, with
379
+ `Typography` for text and `Grid` for layout — Comet themes `Card`, `Paper`, and `Typography`, so they
380
+ carry the right elevation, radius, and type scale without custom CSS, while `Grid` takes its spacing
381
+ from the theme. `Card`, `CardHeader`, `CardContent`, `Paper`, `Typography`, and `Grid` come from
382
+ `@mui/material`; `FieldSet` and `FormSection` from `@dextinity/admin`.
383
+
384
+ ```tsx
385
+ // Avoid — a Box hand-styled into a titled, bordered panel
386
+ <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, padding: 2 }}>
387
+ <Typography variant="h4">{title}</Typography>
388
+ {children}
389
+ </Box>;
390
+
391
+ // Prefer — FieldSet groups content under a title (collapsible by default)
392
+ <FieldSet title={title} supportText={supportText}>
393
+ {children}
394
+ </FieldSet>;
395
+
396
+ // Prefer — a dashboard widget from @dextinity/cms-admin's ready-made container
397
+ <DashboardWidgetRoot header={title}>{children}</DashboardWidgetRoot>;
398
+ ```
399
+
400
+ ### Buttons: `Button` variants and action buttons, not hand-styled buttons
401
+
402
+ Give `Button` a `variant` rather than styling a button by hand or setting MUI's `color` directly.
403
+ The variants are `primary`, `secondary`, `outlined`, `destructive`, `success`, `textLight`, and
404
+ `textDark`. For common actions, prefer the specialized buttons: `SaveButton`, `CancelButton`,
405
+ `DeleteButton`, and `OkayButton` each carry a suitable variant, an icon, and a translated label.
406
+ `SaveButton` also has built-in loading, success, and error feedback (it is a `FeedbackButton`), so
407
+ you don't hand-build that; use `FeedbackButton` for other async actions, and `CopyToClipboardButton`
408
+ to copy text with a confirmation.
409
+
410
+ ```tsx
411
+ // Avoid — a hand-styled button, and a Save button rebuilt from a plain Button
412
+ <button style={{ background: "#c00", color: "#fff" }} onClick={onDelete}>
413
+ Delete
414
+ </button>;
415
+ <Button variant="primary" startIcon={<Save />}>
416
+ Save
417
+ </Button>;
418
+
419
+ // Prefer — specialized buttons carry variant, icon, and label (SaveButton adds save feedback)
420
+ <DeleteButton onClick={onDelete} />;
421
+ <SaveButton onClick={onSave} />;
422
+ ```
423
+
424
+ ### Date and time: pickers from `@dextinity/admin`, not raw inputs
425
+
426
+ Enter dates and times through the picker components rather than a plain text input or an MUI picker
427
+ configured by hand. `@dextinity/admin` exports `DatePicker`, `DateTimePicker`, and `TimePicker` (with
428
+ `DateRangePicker` and `DateTimeRangePicker` for ranges), plus `DatePickerField` and siblings for use
429
+ as Final Form fields. Each picker
430
+ manages its own value format — `DatePicker`, for example, reads and writes an ISO `YYYY-MM-DD`
431
+ string — so you don't parse or format dates by hand. The pickers need MUI X's `LocalizationProvider`
432
+ at the app root, set up once with `AdapterDateFns`; pass `adapterLocale` to localize.
433
+
434
+ ```tsx
435
+ // Avoid — a plain text input used as a date field
436
+ <input type="text" value={value} onChange={(event) => onChange(event.target.value)} />;
437
+
438
+ // Prefer — a DatePicker working with ISO date strings
439
+ <DatePicker value={value} onChange={onChange} />;
440
+
441
+ // The pickers need a LocalizationProvider at the app root, set up once
442
+ import { LocalizationProvider } from "@mui/x-date-pickers";
443
+ import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
444
+
445
+ <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={locale}>
446
+ {children}
447
+ </LocalizationProvider>;
448
+ ```
449
+
450
+ ### Feedback and overlays: `Alert`, `Loading`, `Dialog`, `Tooltip`, not hand-built ones
451
+
452
+ Show status, loading, dialogs, and tooltips through the components rather than assembling them from
453
+ `div`s and state. `Alert` takes a `severity` (`info`, `warning`, `error`, `success`), a `title`, an
454
+ `action`, and an `onClose`. `Loading` renders the standard spinner; its `behavior` prop (`auto`,
455
+ `fillParent`, `fillParentAbsolute`, `fillPageHeight`) sets whether it renders inline, fills its
456
+ parent, or fills the page. Use `Dialog` and `Tooltip` from `@dextinity/admin` — Comet's own wrappers, not
457
+ MUI's directly. For a transient confirmation, call `showSnackbar()` from `useSnackbarApi()` with a
458
+ snackbar element — Comet's `UndoSnackbar`, or a MUI `Snackbar` wrapping an `Alert` — and mount
459
+ `SnackbarProvider` near the app root.
460
+
461
+ ```tsx
462
+ // Avoid — a hand-built alert box and a hand-built spinner
463
+ <div style={{ background: "#fdecea", padding: 12 }}>{errorMessage}</div>;
464
+ {
465
+ loading && <div className="spinner" />;
466
+ }
467
+
468
+ // Prefer — Alert shows severity; Loading renders the standard spinner
469
+ <Alert severity="error">{errorMessage}</Alert>;
470
+ {
471
+ loading && <Loading />;
472
+ }
473
+ ```
474
+
475
+ ### Icons: `@dextinity/admin-icons`, not ad-hoc SVGs
476
+
477
+ Take icons from `@dextinity/admin-icons` rather than importing SVG files or an arbitrary
478
+ icon from another set, so they match the design system and stay consistent. Each icon is a named
479
+ export built on MUI's `SvgIcon`, so size it with the `fontSize` prop (`small`, `medium`, `large`) and
480
+ color it with the `color` prop — the icons use `currentColor`.
481
+
482
+ ```tsx
483
+ // Avoid — an imported SVG file, or an arbitrary icon from another set
484
+ import deleteIcon from "./delete.svg";
485
+
486
+ <img src={deleteIcon} width={16} alt="" />;
487
+
488
+ // Prefer — a named icon from the Comet set, sized and colored through props
489
+ import { Delete } from "@dextinity/admin-icons";
490
+
491
+ <Delete fontSize="small" color="error" />;
492
+ ```