9.0.0
@comet/admin@9.0.0
Major Changes
-
5f1566a: Make packages ESM-only
-
99140f8: Bump MUI X Data Grid peer dependency to v8
See the migration guide for information on how to upgrade.
-
3fda20b: Remove the "Future" prefix from date/time components as they are now considered stable
If already in use, update the imports of these components and their types:
DatePicker:
-
Future_DatePicker->DatePicker -
Future_DatePickerProps->DatePickerProps -
Future_DatePickerClassKey->DatePickerClassKey -
Future_DatePickerField->DatePickerField -
Future_DatePickerFieldProps->DatePickerFieldPropsDateRangePicker:
-
Future_DateRangePicker->DateRangePicker -
Future_DateRangePickerProps->DateRangePickerProps -
Future_DateRangePickerClassKey->DateRangePickerClassKey -
Future_DateRangePickerField->DateRangePickerField -
Future_DateRangePickerFieldProps->DateRangePickerFieldPropsTimePicker:
-
Future_TimePicker->TimePicker -
Future_TimePickerProps->TimePickerProps -
Future_TimePickerClassKey->TimePickerClassKey -
Future_TimePickerField->TimePickerField -
Future_TimePickerFieldProps->TimePickerFieldPropsDateTimePicker:
-
Future_DateTimePicker->DateTimePicker -
Future_DateTimePickerProps->DateTimePickerProps -
Future_DateTimePickerClassKey->DateTimePickerClassKey -
Future_DateTimePickerField->DateTimePickerField -
Future_DateTimePickerFieldProps->DateTimePickerFieldPropsIf your theme is using
defaultPropsorstyleOverridesfor any of these components, update their component-keys: -
CometAdminFutureDatePicker->CometAdminDatePicker -
CometAdminFutureDateRangePicker->CometAdminDateRangePicker -
CometAdminFutureTimePicker->CometAdminTimePicker -
CometAdminFutureDateTimePicker->CometAdminDateTimePickerIf you are using class-names to access these components' slots, update them:
-
CometAdminFuture_DatePicker-*->CometAdminDatePicker-* -
CometAdminFuture_DateRangePicker-*->CometAdminDateRangePicker-* -
CometAdminFuture_TimePicker-*->CometAdminTimePicker-* -
CometAdminFuture_DateTimePicker-*->CometAdminDateTimePicker-*
-
-
fd5c36f: Remove
clearableprop fromAutocomplete,FinalFormInput,FinalFormNumberInputandFinalFormSearchTextFieldThose fields are now clearable automatically when not set to
required,disabledorreadOnly. -
3c81ff0: Remove
hasClearableContentprop fromClearInputAdornmentThe component now always renders when included in the component tree. Callers should conditionally render the component instead of passing the
hasClearableContentprop.Migration:
Before:
<ClearInputAdornment position="end" hasClearableContent={Boolean(value)} onClick={() => onChange("")} />
After:
{ value && <ClearInputAdornment position="end" onClick={() => onChange("")} />; }
-
631540c: Rename
variantprop ofTooltiptocolorand remove theneutralandprimaryoptions<Tooltip title="Title" - variant="light" + color="light" > <Info /> </Tooltip><Tooltip title="Title" - variant="neutral" > <Info /> </Tooltip> -
9cb3f95: Bump MUI X Date Pickers peer dependency to v8
See the migration guide for information on how to upgrade.
Minor Changes
-
15e771b: Add
TimeRangePickerandTimeRangePickerFieldcomponentsThese MUI X-based components complete the set of date/time components in
@comet/adminand replace the deprecatedTimeRangePicker,TimeRangeFieldandFinalFormTimeRangePickerfrom@comet/admin-date-time.The
valueand the value passed toonChangeare aTimeRangeobject withstartandendas 24-hour time strings (HH:mm):type TimeRange = { start: string | null; end: string | null; };
-
d7b77af: Add
ErrorHandlerProvideranduseErrorHandlerfor centralized error reporting fromErrorBoundaryErrorBoundarynow invokes an optionalonError(error, errorInfo)callback provided throughErrorHandlerProvider's context whenever it catches an error. The visible Alert UI ofErrorBoundaryis unchanged — the callback is purely additive and intended for forwarding errors to a reporting service (e.g., Sentry).Example
import { ErrorHandlerProvider } from "@comet/admin"; <ErrorHandlerProvider onError={(error, errorInfo) => { // Report the error to your error tracking service console.error(error, errorInfo.componentStack); }} > {children} </ErrorHandlerProvider>;
-
8c2fdde: Add filtering and sorting to
DependenciesListandDependentsListUsers can now filter dependencies/dependents by name, type, secondary information, and visibility, and sort by all columns. A default filter shows only visible items. The
GqlFiltertype is now exported from@comet/admin.Breaking changes:
@comet/cms-api:DependencyFilter.targetGraphqlObjectTypeandDependentFilter.rootGraphqlObjectTypechanged fromstringtoStringFilter. Update any code passing a plain string to use{ equal: "..." }instead.@comet/cms-api:DependenciesService.getDependents()andgetDependencies()consolidated thefilter,paginationArgs, andoptionsparameters into a singleoptionsobject. If you call these methods directly, merge the arguments:// Before service.getDependents(target, filter, { offset, limit }, { forceRefresh, sort }); // After service.getDependents(target, { filter, offset, limit, forceRefresh, sort });
@comet/cms-admin: The GQL queries passed toDependenciesListandDependentsListmust now accept$filterand$sortvariables and forward them to thedependencies/dependentsfield. Update your queries as follows:# DependentsList query MyDependents($id: ID!, $offset: Int!, $limit: Int!, $forceRefresh: Boolean = false, $filter: DependentFilter, $sort: [DependencySort!]) { item: myEntity(id: $id) { id dependents(offset: $offset, limit: $limit, forceRefresh: $forceRefresh, filter: $filter, sort: $sort) { nodes { rootGraphqlObjectType rootId rootColumnName jsonPath name secondaryInformation visible } totalCount } } } # DependenciesList query MyDependencies($id: ID!, $offset: Int!, $limit: Int!, $forceRefresh: Boolean = false, $filter: DependencyFilter, $sort: [DependencySort!]) { item: myEntity(id: $id) { id dependencies(offset: $offset, limit: $limit, forceRefresh: $forceRefresh, filter: $filter, sort: $sort) { nodes { targetGraphqlObjectType targetId rootColumnName jsonPath name secondaryInformation visible } totalCount } } }
-
f066335: Add support for React 19
-
8e3a074: New helper: downloadFile, to be used instead of file-saver dependency
-
2fe9d4b: Add support for translating page and document content
Content translation can now be applied to entire documents at once, in addition to the existing field-level translation.
Setup
Wrap the application with
AzureAiTranslatorProvider(supportsbatchTranslateautomatically):<AzureAiTranslatorProvider enabled showApplyTranslationDialog> {children} </AzureAiTranslatorProvider>
Making a document type translatable
Add
createDocumentTranslationMethodsand theTranslatableInterfacetype to the document definition:import { createDocumentTranslationMethods, type TranslatableInterface } from "@comet/cms-admin"; const rootBlocks = { content: PageContentBlock, seo: SeoBlock, }; export const Page: DocumentInterface & TranslatableInterface & DependencyInterface = { // ...existing config ...createDocumentRootBlocksMethods(rootBlocks), ...createDocumentTranslationMethods(rootBlocks), };
Adding translate action to the edit page
createUsePagenow returns atranslateContentfunction. Use it withTranslateContentMenuIteminside aCrudMoreActionsMenu:const { translateContent /* ...other fields */ } = usePage({ pageId: id }); <CrudMoreActionsMenu overallActions={[<TranslateContentMenuItem translateContent={translateContent} />]} />;
Page tree integration
The page tree context menu and bulk action toolbar automatically show a "Translate" action for pages. This translates the page name, slug, and document content.
-
460cbfb: Add translation support for textarea fields in
FinalFormInputPreviously, only
type="text"fields supported content translation. Nowtype="textarea"fields (used byTextAreaField) also show the translate button and use a multiline translation dialog.
Patch Changes
-
92281f1: Add
"sideEffects"to package.json for better tree-shakability -
b4ba869: Fix Data Grid Excel export for
singleSelectcolumnsWhen a
singleSelectcolumn usesvalueOptions, Excel export now resolves the exported cell value to the matching option label unless a customvalueFormatteroverrides it. -
57678d0: Fix
FinalFormNumberInputdirtying the form on mount when the initial value isnullThe value-sync effect inside
FinalFormNumberInputran on mount and calledinput.onChange(undefined)wheneverinput.valuewas empty. For a form whose initial value wasnull, this silently normalized the value toundefined, making the fielddirtybefore any user interaction and breaking dirty-tracking features such asEditDialog,SaveBoundary, and the unsaved-changes router prompt.The mount-time sync now only updates the formatted display string. The empty-input normalization to
undefinedstill happens on real user interaction inhandleBlur. -
fdabaf1: Fix
MainContentwithfullHeightgrowing past the viewport after returning from a scrolled detail pageuseTopOffsetusedgetBoundingClientRect().top, which is viewport-relative. When navigating back from a scrolled page, the browser could restore the previous scroll position before the offset was measured, producing a too-small offset and acalc(100vh - topOffset)larger than the viewport. The offset is now measured relative to the document by addingwindow.scrollY. -
8e40458: Fix
Stackcrash when location changes before any breadcrumb is registeredStackaccessed the last entry of an empty breadcrumb array on location change, causing aTypeError: Cannot set properties of undefined. Guarded the update to skip when no breadcrumbs are registered yet. -
b459ec7: Reduce published package size by keeping non-runtime build artifacts out of the bundle
-
cabba53: Fix
DataGridPaginationpage information not pluralizing "items"The default message for
comet.dataGridPagination.pageInformationused a fixed plural form, forcing translations to follow the same shape. In languages with distinct singular/plural forms, this produced an incorrect result whenitemsTotal === 1. The default message now uses ICUpluralsyntax so translators can branch on count.Projects that maintain their own translation of
comet.dataGridPagination.pageInformationshould update it to usepluralwithoneandotherbranches.
@comet/admin-babel-preset@9.0.0
Major Changes
- 5f1566a: Make packages ESM-only
@comet/admin-color-picker@9.0.0
Major Changes
- 5f1566a: Make packages ESM-only
Minor Changes
- f066335: Add support for React 19
Patch Changes
- 92281f1: Add
"sideEffects"to package.json for better tree-shakability
@comet/admin-date-time@9.0.0
Major Changes
-
3fda20b: Add a "Legacy" prefix to class-names and theme component-keys
This affects the components of which their new counterparts are now considered stable in
@comet/admin.Consider using the new components from
@comet/adminIn most cases, the new components will be a drop-in replacement for the legacy components, so you can simply replace the imports:
| Legacy component from
@comet/admin-date-time| New component from@comet/admin|
| ---------------------------------------------- | -------------------------------------------------- |
|DatePicker|DatePicker|
|DateField|DatePickerField|
|FinalFormDatePicker|DatePickerField(without using<Field />) |
|DateRangePicker|DateRangePicker|
|DateRangeField|DateRangePickerField|
|FinalFormDateRangePicker|DateRangePickerField(without using<Field />) |
|TimePicker|TimePicker|
|TimeField|TimePickerField|
|FinalFormTimePicker|TimePickerField(without using<Field />) |
|DateTimePicker|DateTimePicker|
|DateTimeField|DateTimePickerField|
|FinalFormDateTimePicker|DateTimePickerField(without using<Field />) |To continue using the existing components, the following changes will need to be made:
Update any use of class-names of the component's slots:
-
CometAdminDatePicker-*->CometAdminLegacyDatePicker-* -
CometAdminDateRangePicker-*->CometAdminLegacyDateRangePicker-* -
CometAdminDateTimePicker-*->CometAdminLegacyDateTimePicker-* -
CometAdminTimePicker-*->CometAdminLegacyTimePicker-*Update the component-keys when using
defaultPropsorstyleOverridesin the theme: -
CometAdminDatePicker->CometAdminLegacyDatePicker -
CometAdminDateRangePicker->CometAdminLegacyDateRangePicker -
CometAdminDateTimePicker->CometAdminLegacyDateTimePicker -
CometAdminTimePicker->CometAdminLegacyTimePicker
-
-
15e771b: Deprecate
TimeRangePicker,TimeRangeFieldandFinalFormTimeRangePickerand add a "Legacy" prefix to their class-names and theme component-keyTheir new counterparts in
@comet/adminare now considered stable.Consider using the new components from
@comet/adminIn most cases, the new components will be a drop-in replacement for the legacy components, so you can simply replace the imports:
| Legacy component from
@comet/admin-date-time| New component from@comet/admin|
| ---------------------------------------------- | -------------------------------------------------- |
|TimeRangePicker|TimeRangePicker|
|TimeRangeField|TimeRangePickerField|
|FinalFormTimeRangePicker|TimeRangePickerField(without using<Field />) |To continue using the existing component, the following changes will need to be made:
Update any use of class-names of the component's slots:
-
CometAdminTimeRangePicker-*->CometAdminLegacyTimeRangePicker-*Update the component-key when using
defaultPropsorstyleOverridesin the theme: -
CometAdminTimeRangePicker->CometAdminLegacyTimeRangePicker
-
-
5f1566a: Make packages ESM-only
Minor Changes
-
1a83c01: Deprecate the
@comet/admin-date-timepackageAll of its components now have stable replacements in
@comet/admin. The remaining exports (DatePickerNavigation,DateFnsLocaleProvider,DateFnsLocaleContextanduseDateFnsLocale) are now marked as deprecated as well.See the migration guide for how to migrate to the new components in
@comet/admin. -
f066335: Add support for React 19
Patch Changes
- 92281f1: Add
"sideEffects"to package.json for better tree-shakability
@comet/admin-generator@9.0.0
Major Changes
-
99140f8: Bump MUI X Data Grid peer dependency to v8
See the migration guide for information on how to upgrade.
-
0ec748a: Convert to ESM
To upgrade, make the following changes to your
tsconfig.json:{ "compilerOptions": { - "module": "ESNext", - "moduleResolution": "Node", + "module": "preserve", + "moduleResolution": "bundler" } }
Minor Changes
- f1e6145: Add
initialPageSizeoption toGridConfigfor the Admin Generator. When set, the value is passed aspageSizetouseDataGridRemotein the generated DataGrid code. - f066335: Add support for React 19
@comet/admin-icons@9.0.0
Major Changes
- 5f1566a: Make packages ESM-only
Minor Changes
- f066335: Add support for React 19
Patch Changes
- 92281f1: Add
"sideEffects"to package.json for better tree-shakability
@comet/admin-rte@9.0.0
Major Changes
- 5f1566a: Make packages ESM-only
Minor Changes
-
f066335: Add support for React 19
-
2fe9d4b: Add support for translating page and document content
Content translation can now be applied to entire documents at once, in addition to the existing field-level translation.
Setup
Wrap the application with
AzureAiTranslatorProvider(supportsbatchTranslateautomatically):<AzureAiTranslatorProvider enabled showApplyTranslationDialog> {children} </AzureAiTranslatorProvider>
Making a document type translatable
Add
createDocumentTranslationMethodsand theTranslatableInterfacetype to the document definition:import { createDocumentTranslationMethods, type TranslatableInterface } from "@comet/cms-admin"; const rootBlocks = { content: PageContentBlock, seo: SeoBlock, }; export const Page: DocumentInterface & TranslatableInterface & DependencyInterface = { // ...existing config ...createDocumentRootBlocksMethods(rootBlocks), ...createDocumentTranslationMethods(rootBlocks), };
Adding translate action to the edit page
createUsePagenow returns atranslateContentfunction. Use it withTranslateContentMenuIteminside aCrudMoreActionsMenu:const { translateContent /* ...other fields */ } = usePage({ pageId: id }); <CrudMoreActionsMenu overallActions={[<TranslateContentMenuItem translateContent={translateContent} />]} />;
Page tree integration
The page tree context menu and bulk action toolbar automatically show a "Translate" action for pages. This translates the page name, slug, and document content.
Patch Changes
- 92281f1: Add
"sideEffects"to package.json for better tree-shakability
@comet/brevo-admin@9.0.0
Major Changes
-
99140f8: Bump MUI X Data Grid peer dependency to v8
See the migration guide for information on how to upgrade.
-
9cb3f95: Bump MUI X Date Pickers peer dependency to v8
See the migration guide for information on how to upgrade.
Minor Changes
- f066335: Add support for React 19
Patch Changes
-
92281f1: Add
"sideEffects"to package.json for better tree-shakability -
51f25f1: Fix
BrevoContactFormsendingsendDoubleOptInin update mutationsendDoubleOptInis not a valid field ofBrevoContactUpdateInputand only applies when creating a contact. The field was being included in the update payload because it was initialized in form state but not stripped out before the update mutation was called.
@comet/cms-admin@9.0.0
Major Changes
-
8c2fdde: Add filtering and sorting to
DependenciesListandDependentsListUsers can now filter dependencies/dependents by name, type, secondary information, and visibility, and sort by all columns. A default filter shows only visible items. The
GqlFiltertype is now exported from@comet/admin.Breaking changes:
@comet/cms-api:DependencyFilter.targetGraphqlObjectTypeandDependentFilter.rootGraphqlObjectTypechanged fromstringtoStringFilter. Update any code passing a plain string to use{ equal: "..." }instead.@comet/cms-api:DependenciesService.getDependents()andgetDependencies()consolidated thefilter,paginationArgs, andoptionsparameters into a singleoptionsobject. If you call these methods directly, merge the arguments:// Before service.getDependents(target, filter, { offset, limit }, { forceRefresh, sort }); // After service.getDependents(target, { filter, offset, limit, forceRefresh, sort });
@comet/cms-admin: The GQL queries passed toDependenciesListandDependentsListmust now accept$filterand$sortvariables and forward them to thedependencies/dependentsfield. Update your queries as follows:# DependentsList query MyDependents($id: ID!, $offset: Int!, $limit: Int!, $forceRefresh: Boolean = false, $filter: DependentFilter, $sort: [DependencySort!]) { item: myEntity(id: $id) { id dependents(offset: $offset, limit: $limit, forceRefresh: $forceRefresh, filter: $filter, sort: $sort) { nodes { rootGraphqlObjectType rootId rootColumnName jsonPath name secondaryInformation visible } totalCount } } } # DependenciesList query MyDependencies($id: ID!, $offset: Int!, $limit: Int!, $forceRefresh: Boolean = false, $filter: DependencyFilter, $sort: [DependencySort!]) { item: myEntity(id: $id) { id dependencies(offset: $offset, limit: $limit, forceRefresh: $forceRefresh, filter: $filter, sort: $sort) { nodes { targetGraphqlObjectType targetId rootColumnName jsonPath name secondaryInformation visible } totalCount } } }
-
ee24125: Remove
createHttpClientfunctionUse native fetch instead.
-
5f1566a: Make packages ESM-only
-
99140f8: Bump MUI X Data Grid peer dependency to v8
See the migration guide for information on how to upgrade.
-
790e8d0: Remove the
filesInfoTextslot fromFileSelect -
85b09a2: Replace
DependencyListwithDependenciesListandDependentsListBreaking change:
DependencyListhas been removed. UseDependenciesListfor queries returningitem.dependenciesandDependentsListfor queries returningitem.dependents. -
171c335: Redirects: add
domainsource typeTo fully support domain redirects, additional handling is required in the site middleware.
Minor Changes
-
4c1aeb2: Add
noFollowoption toExternalLinkBlockEditors can now mark an external link as
nofollowvia a new checkbox in the admin form. When enabled, the rendered<a>tag receivesrel="nofollow". Existing links are unaffected by an automatic block-data migration that setsnoFollowtofalse. -
dc8f29c: Add
SitePreviewActiontoDocumentInterfaceAllows overriding the site preview button in the page tree row actions on a per-document-type basis. When set, the provided component replaces the default preview
RowActionsItem, enabling custom preview URL construction (e.g., using additional GraphQL queries or scope data).Example
import { RowActionsItem } from "@comet/admin"; import { Preview } from "@comet/admin-icons"; import { type DocumentInterface, openSitePreviewWindow, type SitePreviewActionProps } from "@comet/cms-admin"; function PageSitePreviewAction({ pageTreeNode }: SitePreviewActionProps) { // Use hooks to construct a custom preview URL const previewPath = useCustomPreviewPath(pageTreeNode); return ( <RowActionsItem icon={<Preview />} disabled={!previewPath} onClick={() => { if (previewPath) { openSitePreviewWindow(previewPath, "/custom-root"); } }} > Open preview </RowActionsItem> ); } export const Page: DocumentInterface = { // ... SitePreviewAction: PageSitePreviewAction, };
-
c0cee12: Add
placeholdersoption tocreateTipTapRichTextBlockthat allows inserting pre-defined placeholder tokens into the rich text editor. Placeholders are rendered as non-editable chips and can only be removed as a whole unit. -
7fbe2a7: Add
allowPageDeleteoption to disable deletion of pages in the PageTree. When set tofalse, the delete option is hidden in the Admin UI and the API blocks deletion attempts. -
d7b77af: Add
onErrortoCometConfigfor centralized error reporting from all error boundariesCometConfigProvidernow accepts an optionalonError(error, errorInfo)callback that is invoked whenever any descendantErrorBoundarycatches an error. Use this to forward errors to a reporting service such as Sentry.Example
<CometConfigProvider {...config} onError={(error, errorInfo) => { // Report the error to your error tracking service console.error(error, errorInfo.componentStack); }} > {children} </CometConfigProvider>
-
f066335: Add support for React 19
-
c6703db: Export
ChooseDamFilesDialogAllows building custom multi-file picker UIs on top of the DAM file dialog (e.g. bulk-adding files to a list block).
import { ChooseDamFilesDialog } from "@comet/cms-admin"; <ChooseDamFilesDialog open={open} onClose={onClose} onConfirm={(fileIds) => ...} initialFileIds={[]} allowedMimetypes={["image/jpeg"]} />
-
8cb0844: Export
isLinkTargetandvalidateLinkTarget -
127a492: Add TipTapRichTextBlock as an alternative to RichTextBlock
-
c6703db: Add
multipleprop toFileFieldfor selecting multiple DAM filesFileFieldnow acceptsmultiple={true}to select a list of DAM files instead of a single file. Multi-file values are typed asGQLDamFileFieldFileFragment[](the same fragment used in single-file mode); the component renders a stacked list of files with per-row menu and remove actions. The picker dialog pre-checks the current selection viainitialFileIdsand returns the picked file ids on confirm. The single-file API is unchanged.Example
<Field name="files" component={FileField} multiple preview={(file) => <Thumbnail fileId={file.id} />} />
-
25f7342: Export
PageTreeSelect -
71dce06: Make DataGrid columns in DAM sortable
Make Name, Type/Format, Info, Creation, and Latest Change columns in the
FolderDataGridsortable via column header clicks, using the standardmuiGridSortToGqlpattern from generated grids. Remove the separate Sort dropdown from the toolbar. Sort state is now stored in URL params instead of localStorage. -
7ab96c2: Add
SearchHeaderItemcomponent for full-text searchThe
SearchHeaderItemcomponent renders a search input (intended for the header) that opens a dropdown with the results of themyFullTextSearchquery. The search is restricted to the currently selected content scope. Clicking a result opens the corresponding entity using theentityDependencyMapfrom theDependenciesConfig(the same mechanism used by warnings and dependencies).Example
import { Header, SearchHeaderItem } from "@comet/cms-admin"; <Header> <SearchHeaderItem /> <ContentScopeControls /> <UserHeaderItem /> </Header>;
-
8ad9dd8: Add support for deleting multiple redirects in the grid
-
bc57b4a: Add support for child blocks in
createTipTapRichTextBlockChild blocks can now be embedded into the TipTap rich text editor, similar to the existing link feature. Configure the supported blocks via the new
childBlocksoption (both admin and API): a record keyed by a stable key, where each entry is{ block, display }.displayis either"block"(a standalone block element on its own line) or"inline"(rendered inline within the surrounding text). A "+" button in the toolbar opens a menu listing the configured child blocks. Selecting one opens a dialog with the block's Admin component; on confirmation, the block is inserted into the editor as a non-editable preview that can be edited (by clicking it) or removed.Example
createTipTapRichTextBlock({ childBlocks: { productPrice: { block: ProductPriceBlock, display: "inline" } }, });
-
2fe9d4b: Add support for translating page and document content
Content translation can now be applied to entire documents at once, in addition to the existing field-level translation.
Setup
Wrap the application with
AzureAiTranslatorProvider(supportsbatchTranslateautomatically):<AzureAiTranslatorProvider enabled showApplyTranslationDialog> {children} </AzureAiTranslatorProvider>
Making a document type translatable
Add
createDocumentTranslationMethodsand theTranslatableInterfacetype to the document definition:import { createDocumentTranslationMethods, type TranslatableInterface } from "@comet/cms-admin"; const rootBlocks = { content: PageContentBlock, seo: SeoBlock, }; export const Page: DocumentInterface & TranslatableInterface & DependencyInterface = { // ...existing config ...createDocumentRootBlocksMethods(rootBlocks), ...createDocumentTranslationMethods(rootBlocks), };
Adding translate action to the edit page
createUsePagenow returns atranslateContentfunction. Use it withTranslateContentMenuIteminside aCrudMoreActionsMenu:const { translateContent /* ...other fields */ } = usePage({ pageId: id }); <CrudMoreActionsMenu overallActions={[<TranslateContentMenuItem translateContent={translateContent} />]} />;
Page tree integration
The page tree context menu and bulk action toolbar automatically show a "Translate" action for pages. This translates the page name, slug, and document content.
Patch Changes
-
92281f1: Add
"sideEffects"to package.json for better tree-shakability -
1475f4a: Hide selective actions of DAM more actions menu in
ChooseDamFileDialog -
3cbf0ff: Show
ArchivedTagnext to the title on the DAM file detail pagePreviously the archived state was only visible in the DAM file list, which could be confusing when opening an archived file's detail page via link.
-
8a93124: Fix
hideContextMenunot hiding the context menu column in the DAMDataGridThe visibility flag was applied to a no-longer-existing
contextMenucolumn id; the column had been renamed toactions. The flag now targets the correct column. -
0e9189b: Export
AnonymousBlockInterfacetype -
fa5c7a4: Fix
FileFieldbreaking image block selectionThe
DamFileFieldFilefragment lost the image dimensions (width,height,cropArea) needed byDamImageBlock/PixelImageBlock. Selecting an image inside an image block crashed because those fields were missing. Restored them on the fragment.Composing the fragment into a parent collection (e.g. a many-to-many to
DamFile) exposed a Mikro-ORM gotcha:Collection.loadItems()does not honoreager: true, so each loadedDamFilehad an uninitializedimageReference and GraphQL threwCannot return null for non-nullable field DamFileImage.width. Added animage@ResolveFieldonFilesResolverthat initializes the Reference if needed, so consumers don't have to remember to populate it. -
5d006c1: Fix
1-NaN of NaNpagination footer androwCountwarning in DAMFolderDataGridThe grid now routes
totalCountthroughuseBufferedRowCount, sorowCountstays a number across refetches instead of becomingundefinedwhile data is loading. -
31d9296: Fix duplicate TipTap
'link'extension warning by explicitly disabling StarterKit's built-in Link extensionStarterKit (v3+) includes
@tiptap/extension-linkby default. Since we register our ownCmsLinkmark (also named"link"), this caused a "Duplicate extension names found: ['link']" warning. Settinglink: falseinStarterKit.configure()resolves this. -
c7f80e9: Fix page search not expanding the tree when navigating between matches after "Collapse all"
Previously, jumping to the next or previous search match only scrolled to the match without expanding its collapsed ancestors. After collapsing the tree via "Collapse all" during an active search, continuing the search revealed nothing. Navigating between matches now re-expands the current match's ancestors before scrolling to it.
-
ae85ba9: Fix
TipTapRichTextBlocktoolbar colors to match the existingRichTextBlocktoolbarThe TipTap toolbar incorrectly used Comet's
greyPalette(wheregreyPalette[100]is#D9D9D9) for the toolbar background, button icon, hover, and disabled states. This made the toolbar look noticeably darker than the existing Draft.js-basedRichTextBlocktoolbar, which uses MUI's lightergreypalette (grey[100]is#F5F5F5). The TipTap toolbar now uses the same MUI grey shades for these states so the two toolbars look consistent. -
b459ec7: Reduce published package size by keeping non-runtime build artifacts out of the bundle
-
f29b2d7: Deprecate
ChooseFileDialogexportChooseFileDialogwas renamed toChooseDamFileDialog -
5e87236: Remove pagination from
StartBuildsDialogdata gridThe
buildTemplatesquery always returns all templates, so the page-based pagination in the dialog grid was misleading. All templates are now displayed at once. -
4729b3f: Prevent links in the
TableBlockRTE cell preview from opening when editing a cellDouble-clicking a cell to edit it would open any link in the preview.
Pointer events are now disabled on the preview, while text selection still works. -
ab5e547: Validate the SEO block's structured data field as JSON
The structured data field in the SEO block now shows a validation error when the entered value is not valid JSON. This matches the existing API-side
@IsJSON()validation and prevents invalid payloads from being saved.
@comet/agent-features@9.0.0
Minor Changes
- ca5b0fa: Add
@comet/agent-featurespackage containing skills and rules for AI coding agents
@comet/api-generator@9.0.0
Major Changes
-
18748d1: Stop generating GraphQL selection-set based
populatehandling in generated CRUD list resolvers.MikroORM dataloader must now be enabled in API projects (for example with
dataloader: DataloaderType.ALLin your MikroORM config) to efficiently resolve relation fields. -
10dda8c: Remove
targetDirectoryconfig from@CrudGeneratordecorator and always generate files to${__dirname}/../generated/
Minor Changes
-
623111e: Support re-using an enum (for filters) in multiple models by using a shared generated filter type
-
70a77db: Reuse
@RequiredPermissionfrom entity in resolver permissionsThe
UserPermissionsGuardnow falls back to the entity's@RequiredPermissiondecorator when no permission is explicitly set on the resolver. The entity is resolved from the@Resolver(() => Entity)decorator.Additionally, the CRUD generator no longer emits
@RequiredPermissionon generated resolvers if the entity already has@RequiredPermissionset. This avoids redundant permission declarations. -
dd51208: Update TypeScript compilation target to ES2023 and lib to ES2023 to match the required Node.js v22
Patch Changes
-
8642996: Fix
MODULE_NOT_FOUNDerrors caused by extensionless deep imports of@nestjs/graphqlinternals.@nestjs/graphql13.3.0 tightened itsexportsmap so that the"./*": "./*"pattern no longer maps to.jsautomatically. All deep imports of@nestjs/graphqlinternals now use explicit.jsextensions. -
16c0e64: Fix missing import for nested
ManyToOneresolver target entities@comet/api-generatornow imports nestedManyToOnetarget entities in generated resolvers so generated code compiles without unresolved symbol errors.
@comet/brevo-api@9.0.0
Major Changes
-
77a371e: Prevent importing dev dependencies in the API
Add
import/no-extraneous-dependenciesrule withdevDependenciesrestriction to the NestJS ESLint config, preventing accidental imports of dev-only packages in production source files. Dev dependencies may only be imported in test files.Fix
@comet/brevo-apito correctly declare@nestjs/graphql,graphql,graphql-scalars,lodash.isequal, anduuidas dependencies/peerDependencies instead of devDependencies, since they are imported in source code. -
f932845: Update
@getbrevo/brevoto v5The transactional mail service's
sendmethod now accepts aSendTransacEmailRequest(withoutsender) instead of aSendSmtpEmail. The commonly used fields (to,subject,htmlContent,textContent) are unchanged, so existing calls continue to work.
Minor Changes
- dd51208: Update TypeScript compilation target to ES2023 and lib to ES2023 to match the required Node.js v22
Patch Changes
- ba83625: Remove
node-fetchdependency fromEcgRtrListServicein favor of Node's nativefetch
@comet/cms-api@9.0.0
Major Changes
-
8c2fdde: Add filtering and sorting to
DependenciesListandDependentsListUsers can now filter dependencies/dependents by name, type, secondary information, and visibility, and sort by all columns. A default filter shows only visible items. The
GqlFiltertype is now exported from@comet/admin.Breaking changes:
@comet/cms-api:DependencyFilter.targetGraphqlObjectTypeandDependentFilter.rootGraphqlObjectTypechanged fromstringtoStringFilter. Update any code passing a plain string to use{ equal: "..." }instead.@comet/cms-api:DependenciesService.getDependents()andgetDependencies()consolidated thefilter,paginationArgs, andoptionsparameters into a singleoptionsobject. If you call these methods directly, merge the arguments:// Before service.getDependents(target, filter, { offset, limit }, { forceRefresh, sort }); // After service.getDependents(target, { filter, offset, limit, forceRefresh, sort });
@comet/cms-admin: The GQL queries passed toDependenciesListandDependentsListmust now accept$filterand$sortvariables and forward them to thedependencies/dependentsfield. Update your queries as follows:# DependentsList query MyDependents($id: ID!, $offset: Int!, $limit: Int!, $forceRefresh: Boolean = false, $filter: DependentFilter, $sort: [DependencySort!]) { item: myEntity(id: $id) { id dependents(offset: $offset, limit: $limit, forceRefresh: $forceRefresh, filter: $filter, sort: $sort) { nodes { rootGraphqlObjectType rootId rootColumnName jsonPath name secondaryInformation visible } totalCount } } } # DependenciesList query MyDependencies($id: ID!, $offset: Int!, $limit: Int!, $forceRefresh: Boolean = false, $filter: DependencyFilter, $sort: [DependencySort!]) { item: myEntity(id: $id) { id dependencies(offset: $offset, limit: $limit, forceRefresh: $forceRefresh, filter: $filter, sort: $sort) { nodes { targetGraphqlObjectType targetId rootColumnName jsonPath name secondaryInformation visible } totalCount } } }
-
0e7d7e9: Import blob storage backends dynamically
BlobStorageAzureConfig,BlobStorageAzureStorage,BlobStorageFileConfig, andBlobStorageFileStorageare no longer exported from@comet/cms-apias they are now loaded dynamically based on the configured driver. Only the relevant backend class is imported, which avoids loading unused optional dependencies (e.g.,@azure/storage-bloboraws-sdk). -
3f3da52: Switch to SQL-based entity info system
The
@EntityInfodecorator now accepts a field-path-based object or a raw SQL string instead of a TypeScript function or service class.
This enables efficient SQL-level filtering and sorting of dependencies and warnings based on entity info.Breaking changes:
@EntityInfodecorator API changed: now accepts{ name, secondaryInformation?, visible? }with dot-notation field paths, or a raw SQL stringEntityInfoServiceInterfacehas been removed from exportsPageTreeNodeDocumentEntityInfoServicehas been removed;@EntityInfoonPage,Link, and similar document entities is no longer neededblock_index_dependenciesview exposes two new columnsblockVisibleandentityVisible;visibleis now their logical AND (previously only reflected block-level visibility)block_index_dependenciesview now includesrootName,rootSecondaryInformation,targetName, andtargetSecondaryInformationcolumns fromEntityInfo, removing the need for a runtime JOIN when querying dependencies/dependents
-
962a320: Remove
importDamFileByDownloadmutation -
2ea835c: Rename
RedirectSourceTypeValuestoRedirectSourceType -
171c335: Redirects: add
domainsource typeTo fully support domain redirects, additional handling is required in the site middleware.
Minor Changes
-
f1a473a: Allow
AffectedScopeto return multiple scopesThe
argsToScopefunction can now returnContentScope[]in addition to a singleContentScope. When multiple scopes are returned, the user must have access to all of them (AND relationship).@AffectedScope((args: MyArgs) => [{ domain: args.fromDomain }, { domain: args.toDomain }])
-
8954d64: Add
findUser/findUserOrThrow(andfindUserForLogin/findUserForLoginOrThrow) toUserPermissionsUserServiceInterfaceThe throwing
getUserandgetUserForLoginmethods onUserPermissionsUserServiceInterfaceare now deprecated in favor of paired find methods:-
findUser(id): Promise<User | null>/findUserOrThrow(id): Promise<User> -
findUserForLogin(id): Promise<User | null>/findUserForLoginOrThrow(id): Promise<User>This lets consumers opt into a non-throwing path without wrapping lookups in
try/catch. Existing implementations that only definegetUser/getUserForLogincontinue to work; the service falls back to them.Example
export class UserService implements UserPermissionsUserServiceInterface { async findUser(id: string): Promise<User | null> { return this.users.find((user) => user.id === id) ?? null; } async findUserOrThrow(id: string): Promise<User> { const user = await this.findUser(id); if (!user) { throw new Error(`User not found: ${id}`); } return user; } // ... }
-
-
4c1aeb2: Add
noFollowoption toExternalLinkBlockEditors can now mark an external link as
nofollowvia a new checkbox in the admin form. When enabled, the rendered<a>tag receivesrel="nofollow". Existing links are unaffected by an automatic block-data migration that setsnoFollowtofalse. -
67938b2: Filter
myFullTextSearchresults by the entity's required permissionThe
requiredPermissionof an entity (declared via the@RequiredPermissiondecorator) is now included in both theEntityInfoandEntityInfoFullTextSQL views.The
myFullTextSearchquery (formerlyfullTextSearch) filters results based on the current user's permissions, only returning entities where the user has the required permission. Entries without a required permission are excluded from results, as the permission cannot be determined. Themyprefix reflects that the query operates on the current user and only returns entities the user is allowed to see.Example usage:
@EntityInfo<Product>({ name: "title", secondaryInformation: "category.name", visible: { status: { $eq: ProductStatus.Published } }, fullText: "fullText", }) @RequiredPermission("products") @ObjectType() @Entity() export class Product { // ... }
-
c0cee12: Add
placeholdersoption tocreateTipTapRichTextBlockthat allows inserting pre-defined placeholder tokens into the rich text editor. Placeholders are rendered as non-editable chips and can only be removed as a whole unit. -
7fbe2a7: Add
allowPageDeleteoption to disable deletion of pages in the PageTree. When set tofalse, the delete option is hidden in the Admin UI and the API blocks deletion attempts. -
54f57dd: Support loading data for child blocks embedded in rich text content in
recursivelyLoadBlockDataChild blocks embedded in a
createTipTapRichTextBlockrich text block (stored ascmsBlock/cmsInlineBlocknodes) are now traversed byrecursivelyLoadBlockData, so their registered block loaders run just like for any other nested block. This allows rich text child blocks to load additional data (e.g. via a GraphQL query) without relying on aBlockTransformerServiceon the API side.To support this, the
tipTapContentfield of acreateTipTapRichTextBlockblock is now declared with a dedicatedTipTapRichTextBlockblock meta kind (instead ofJson) that carries the configured child blocks. The block loader uses this kind to detect rich text content instead of inspecting arbitraryJsonfields.Also re-exported from
@comet/site-nextjs. -
a50793a: Add
listFilesmethod toBlobStorageBackendService -
cac2b3b: Add fullText query support for PageTree (pageTreeFullTextSearch query)
To enable add a fullText column for a PageTree document (Page or others):
@Index({ type: "fulltext" }) @Property<Page>({ nullable: true, type: new FullTextType(), onUpdate: (page) => blockToMikroOrmFullText(page.content) }) searchableContent?: string;
and enable fullText option for PageTreeModule
-
9d5f045: Add a
urlTemplatefield resolver toFileImagesResolver -
c6703db: Add
idsfilter todamFilesListFileFilterInputnow accepts an optionalids: [ID!]to restrict the result set to specific files. Useful for batch-loading a known selection (e.g. after a multi-file picker confirms) in a single request. -
b0ceb9c: Export
IsLinkTargetvalidator -
127a492: Add TipTapRichTextBlock as an alternative to RichTextBlock
-
1b37655: Add scope support to
myFullTextSearchThe
EntityInfoFullTextview now includes ascopescolumn, and themyFullTextSearchquery accepts an optionalscopeargument to restrict results to a single content scope.The scopes are derived from either a
scopeproperty on the entity (simple case) or the@ScopedEntitydecorator. To support building the scopes in SQL,@ScopedEntityaccepts a new declarative, SQL-convertible variant in addition to the existing callback/service:// Field path to the scope object (an embeddable) @ScopedEntity("company.scope") // Object mapping scope properties to field paths @ScopedEntity({ companyId: "company.id" }) // Multiple scopes @ScopedEntity(["company.scope", "otherCompany.scope"])
The existing callback and service variants keep working everywhere
@ScopedEntityis used (e.g. the auth guard). They cannot be converted to SQL, so an entity that is part of the full-text index must use the declarative variant (or have ascopeproperty) — otherwise creating theEntityInfoFullTextview throws. -
70a77db: Reuse
@RequiredPermissionfrom entity in resolver permissionsThe
UserPermissionsGuardnow falls back to the entity's@RequiredPermissiondecorator when no permission is explicitly set on the resolver. The entity is resolved from the@Resolver(() => Entity)decorator.Additionally, the CRUD generator no longer emits
@RequiredPermissionon generated resolvers if the entity already has@RequiredPermissionset. This avoids redundant permission declarations. -
8498a7c: Export
EntityInfoObject,EntityInfoFullTextObjectandPaginatedEntityInfoThis allows building custom full-text search resolvers (for example a public, permission-independent site search) that reuse the existing full-text search views and re-use the
entityInforelation to return name and secondary information. -
8ad9dd8: Add support for deleting multiple redirects in the grid
-
bc57b4a: Add support for child blocks in
createTipTapRichTextBlockChild blocks can now be embedded into the TipTap rich text editor, similar to the existing link feature. Configure the supported blocks via the new
childBlocksoption (both admin and API): a record keyed by a stable key, where each entry is{ block, display }.displayis either"block"(a standalone block element on its own line) or"inline"(rendered inline within the surrounding text). A "+" button in the toolbar opens a menu listing the configured child blocks. Selecting one opens a dialog with the block's Admin component; on confirmation, the block is inserted into the editor as a non-editable preview that can be edited (by clicking it) or removed.Example
createTipTapRichTextBlock({ childBlocks: { productPrice: { block: ProductPriceBlock, display: "inline" } }, });
-
2fe9d4b: Add support for translating page and document content
Content translation can now be applied to entire documents at once, in addition to the existing field-level translation.
Setup
Wrap the application with
AzureAiTranslatorProvider(supportsbatchTranslateautomatically):<AzureAiTranslatorProvider enabled showApplyTranslationDialog> {children} </AzureAiTranslatorProvider>
Making a document type translatable
Add
createDocumentTranslationMethodsand theTranslatableInterfacetype to the document definition:import { createDocumentTranslationMethods, type TranslatableInterface } from "@comet/cms-admin"; const rootBlocks = { content: PageContentBlock, seo: SeoBlock, }; export const Page: DocumentInterface & TranslatableInterface & DependencyInterface = { // ...existing config ...createDocumentRootBlocksMethods(rootBlocks), ...createDocumentTranslationMethods(rootBlocks), };
Adding translate action to the edit page
createUsePagenow returns atranslateContentfunction. Use it withTranslateContentMenuIteminside aCrudMoreActionsMenu:const { translateContent /* ...other fields */ } = usePage({ pageId: id }); <CrudMoreActionsMenu overallActions={[<TranslateContentMenuItem translateContent={translateContent} />]} />;
Page tree integration
The page tree context menu and bulk action toolbar automatically show a "Translate" action for pages. This translates the page name, slug, and document content.
-
dd51208: Update TypeScript compilation target to ES2023 and lib to ES2023 to match the required Node.js v22
Patch Changes
-
fa5c7a4: Fix
FileFieldbreaking image block selectionThe
DamFileFieldFilefragment lost the image dimensions (width,height,cropArea) needed byDamImageBlock/PixelImageBlock. Selecting an image inside an image block crashed because those fields were missing. Restored them on the fragment.Composing the fragment into a parent collection (e.g. a many-to-many to
DamFile) exposed a Mikro-ORM gotcha:Collection.loadItems()does not honoreager: true, so each loadedDamFilehad an uninitializedimageReference and GraphQL threwCannot return null for non-nullable field DamFileImage.width. Added animage@ResolveFieldonFilesResolverthat initializes the Reference if needed, so consumers don't have to remember to populate it. -
f6a2932: Fix
MODULE_NOT_FOUNDerrors caused by extensionless deep imports of@nestjs/graphqlinternals.@nestjs/graphql13.3.0 tightened itsexportsmap so that the"./*": "./*"pattern no longer maps to.jsautomatically. All deep imports of@nestjs/graphqlinternals now use explicit.jsextensions. -
35e9e0d: Fix copying the home page creating a second page with the slug
homePreviously, copying the home page produced a second page with the slug
home, after which none of the home pages could be deleted (deleting a page with the slughomeis forbidden). The copy now receives a different slug if the target scope already has a home page, and keepshomeonly when there is none yet. -
91f4a9f: Fix DAM file listing failing for files larger than ~2 GB
The
sizefield ofDamFileandFileUploadwas exposed as a GraphQLInt, which can only represent 32-bit signed integers (max ~2.1 GB). Files exceeding this size (e.g. large videos) caused the GraphQL response to fail withInt cannot represent non 32-bit signed integer value, breaking the DAM file list and the "Select files from DAM" dialog. The field is now exposed asBigInt, which represents the full range of file sizes stored in the database (bigint). -
6b7adc7: Fix
damFilesListreturning no files in subfolders when filtering byidsdamFilesListimplicitly constrains to the scope root when nofolderIdis passed. The constraint already had an escape hatch forfilter.searchText; the same now applies tofilter.ids. Resolving a selection viafilter: { ids: ... }returns all matching files regardless of which folder they live in, which unblocks the adminFileFieldmulti-select for files in subfolders. -
31d9296: Fix duplicate TipTap
'link'extension warning by explicitly disabling StarterKit's built-in Link extensionStarterKit (v3+) includes
@tiptap/extension-linkby default. Since we register our ownCmsLinkmark (also named"link"), this caused a "Duplicate extension names found: ['link']" warning. Settinglink: falseinStarterKit.configure()resolves this. -
19a0528: Fix
MailerLogStatusGQL enum name (was incorrectly registered asWarningStatus) -
71dce06: Support sorting folders by size (child count) in
FoldersService -
f162fa5: Fix
AzureOpenAiContentGenerationServicefor newer GPT modelsWe still used the deprecated
max_tokensthat isn't supported anymore by newer models.
Replaced it with the newermax_completion_tokens. -
1ad7de3: Return null in
getNodeByPathwhen path is/hometo prevent the home page from being returned for that path (results in 404) -
affbb11: Load
jsdomlazily for SVG validationjsdom(~90 MB resident) was imported and instantiated at module load time, so importing anything from@comet/cms-apipulled it into memory even when no SVG was ever validated. It's now loaded on the first SVG validation instead, reducing the package's base memory footprint. -
fad0167: Load
@kubernetes/client-nodelazily inKubernetesService@kubernetes/client-node(~85 MB resident) was statically imported, so importing anything from@comet/cms-apipulled it into memory even for projects that don't use the Kubernetes-based builds feature. The runtime parts of the client are now required lazily whenKubernetesServiceactually connects to a cluster, reducing the package's base memory footprint. -
6793853: Load
openailazily inAzureOpenAiContentGenerationServiceThe
openaiclient was statically imported, so importing anything from@comet/cms-apipulled it into memory even when the content-generation feature was disabled. It's now required lazily when a client is actually created, reducing the package's base memory footprint. -
a2c2eb5: Log the reason when a permission check denies access
AbstractAccessControlService.isEqualOrMorePermissionsnow emits a NestJSLogger.debugline identifying the missing permission or content scope whenever it returnsfalse. TheimpersonationAllowedresolver field additionally logs when a user attempts to impersonate themselves. -
802b0b8: Remove
sharpdependency by parsing the dominant color directly from the 1x1 PNG produced by imgproxy -
8bf0e5b: Remove unused
ts-morphdependency -
ac59b62: Validate uploaded SVGs with
DOMPurify(backed byjsdom) instead of the custom XML-based checkThe previous implementation parsed SVGs with
fast-xml-parserand applied a hand-written allow/deny list to reject scripts, event handlers and unsafe links.
SVGs are now sanitized with the vettedDOMPurifylibrary and rejected when it has to strip any tags or attributes, providing more robust protection against XSS vectors in uploaded SVGs.The
roleattribute and the<use>element are allowed, since they're commonly used in valid SVGs.<use>is restricted to same-document fragment references (e.g.href="#id"); references to external resources are rejected to prevent XSS/SSRF. -
8722deb: Upgrade
file-typedependency from v16 to v21
@comet/cli@9.0.0
Major Changes
-
2529907: Replace
install-agent-skillswithinstall-agent-features— a combined installer for agent skills and agent rulesinstall-agent-featuresinstalls skills fromskills/<name>/SKILL.mdandagentic-plugin/skills/<name>/SKILL.md(folders) and rules fromrules/<name>.md(single markdown files) — both from the local repo and from external git repos listed inagent-features.json. Skills install into.agents/skills/and.claude/skills/; rules install into.agents/rules/,.claude/rules/,.cursor/rules/, and.github/instructions/so they are picked up by Claude Code, Cursor, GitHub Copilot, and other cloud agents. Rules support the same optionalmetadata.internal: truefrontmatter as skills, and may be organized into subdirectories (the layout is preserved in each target).Example
agent-features.json:{ "repos": ["https://github.com/vivid-planet/comet.git"] }Run:
npx @comet/cli install-agent-features
Breaking change: the
install-agent-skillscommand and itsagent-skills.jsonconfig are removed. Migrate by renamingagent-skills.jsontoagent-features.json(the schema is identical) and replacing theinstall-agent-skillsinvocation inpackage.jsonandinstall.shwithinstall-agent-features.
Minor Changes
-
644b4ee: Add node_modules skills and rules discovery to
install-agent-featurescommandThe command now scans direct dependencies in
node_modules(including@scopedpackages) forskills/andrules/directories and creates symlinks to agent-specific directories. This is compatible with the npm-based Agent Skills convention and extends it to also support rules. -
9746947:
install-agent-skills: also install skills fromagentic-plugin/skills/In addition to the existing
skills/directory, the command now installs skills fromagentic-plugin/skills/. This allows shipping agent skills as part of a Claude Code plugin (with a.claude-plugin/plugin.jsonmanifest) without losing the ability to install them viainstall-agent-skills.Both directories are also fetched (via git sparse checkout) when consuming external repos listed in
agent-skills.json.skills/keeps priority overagentic-plugin/skills/.
Patch Changes
-
560a8f2: Cache
getSiteConfigsandop readcalls to avoid redundant executionWhen a template contains multiple placeholders for the same environment,
getSiteConfigs(env)andop readwere called repeatedly with identical arguments. Both are now cached per invocation so each uniqueenvand each uniqueop://URI is resolved only once. -
b459ec7: Reduce published package size by keeping non-runtime build artifacts out of the bundle
@comet/eslint-config@9.0.0
Major Changes
-
23a09c2: Add
curlyESLint rule to enforce braces for control statementsThis rule requires braces around the body of all control statements (if, else, for, while, etc.) to improve code readability and reduce diff size when adding statements.
-
1f903b6: Enable
@typescript-eslint/no-import-type-side-effectsrule -
f51972c: Enable
@graphql-eslint/naming-conventioninreact.jsandnextjs.jsGraphQL code generation appends the operation kind (
Fragment,Query,Mutation,Subscription) to the generated TypeScript type name. Naming an operationFooFragment/FooQuerytherefore produces duplicated types likeGQLFooFragmentFragment/GQLFooQueryQuery. The@graphql-eslint/naming-conventionrule from@graphql-eslint/eslint-pluginreports such names insidegql/graphql-tagged template literals. -
99140f8: Bump MUI X Data Grid peer dependency to v8
See the migration guide for information on how to upgrade.
-
db6b83a: Prevent lib imports from
@comet/packagesUse
no-restricted-importsto prevent importing private files from@comet/*/lib. For example, the following import would be forbidden:import { something } from "@comet/admin/lib/some/private/file";
-
77a371e: Prevent importing dev dependencies in the API
Add
import/no-extraneous-dependenciesrule withdevDependenciesrestriction to the NestJS ESLint config, preventing accidental imports of dev-only packages in production source files. Dev dependencies may only be imported in test files.Fix
@comet/brevo-apito correctly declare@nestjs/graphql,graphql,graphql-scalars,lodash.isequal, anduuidas dependencies/peerDependencies instead of devDependencies, since they are imported in source code. -
695a9c9: Promote
future/*ESLint rules into the main configsThe rules previously only available via
@comet/eslint-config/future/*are now part of the main configs and apply by default. Thefuture/*subpaths are kept as aliases that re-export the main configs, so existing imports continue to work without changes.Newly active rules in the main configs
react.js:react/jsx-no-literals(with a small allowlist of common symbols)@typescript-eslint/consistent-type-exportsformatjs/enforce-default-messageis now enforced as"literal"
nextjs.js:node-cacheis restricted viano-restricted-imports
nestjs.js:node-cacheis restricted viano-restricted-imports(andrestrictedImportPathsis now exported)
-
740dba8: Add support for Next.js 15 and 16
@comet/site-nextjsnow supports Next.js 14, 15, and 16. We recommend using Next.js 16.
Patch Changes
- c57e54e: Allow
console.infoandconsole.debugin theno-consoleESLint rule - 4b1d586: Fix missing Rules of React in
@comet/eslint-config/nextjs.js
@comet/eslint-plugin@9.0.0
Major Changes
-
db6b83a: Prevent lib imports from
@comet/packagesUse
no-restricted-importsto prevent importing private files from@comet/*/lib. For example, the following import would be forbidden:import { something } from "@comet/admin/lib/some/private/file";
@comet/mail-react@9.0.0
Major Changes
-
568ee9a:
MjmlImageis now responsive by defaultThe inner
<img>height scales toautobelow the default breakpoint instead of staying at its declared pixel value, so the image keeps its aspect ratio as its width shrinks on narrow viewports. -
f503e9e:
MjmlDividernow supportsvariant,height,backgroundColor, andbackgroundImageprops, configured throughtheme.divider-
theme.dividerdefines the defaultheightandbackgroundColor -
theme.divider.variantsoverrides those values for named variants, optionally with per-breakpoint responsive values -
theme.divider.backgroundImage(typically a gradient) overlays the bar whilebackgroundColorstays as the solid fallback for clients that don't render gradients -
Per-instance
height,backgroundColor, andbackgroundImageprops override the resolved theme/variant valuesExample theme
import { createTheme } from "@comet/mail-react"; const theme = createTheme({ divider: { defaultVariant: "thin", variants: { thin: { height: "1px", backgroundColor: "#999999" }, thick: { height: { default: "12px", mobile: "8px" }, backgroundColor: "#222222" }, gradient: { backgroundColor: "#5B4FC7", backgroundImage: "linear-gradient(to right, #5B4FC7, #FF6B6B, #FFD166)", }, }, }, }); declare module "@comet/mail-react" { interface DividerVariants { thin: true; thick: true; gradient: true; } }
Usage:
import { MjmlDivider, MjmlMailRoot } from "@comet/mail-react"; <MjmlMailRoot theme={theme}> <MjmlSection> <MjmlColumn> <MjmlDivider /> <MjmlDivider variant="thick" /> <MjmlDivider variant="gradient" /> <MjmlDivider height="2px" backgroundColor="#FF0000" /> </MjmlColumn> </MjmlSection> </MjmlMailRoot>;
Breaking changes
-
The
MjmlDividerprop surface no longer acceptsborderWidth,borderColor,borderStyle,paddingand its variants,width,containerBackgroundColor,align, orcssClass. MigrateborderWidthtoheightandborderColortobackgroundColor, either per-call or ontheme.divider. -
MjmlDividerno longer applies any default padding around the divider. Add spacing through the surrounding section or column (for example withMjmlSpacer). -
<MjmlAttributes><MjmlDivider … /></MjmlAttributes>no longer sets defaults forMjmlDivider. Configure defaults throughtheme.dividerinstead.
-
Minor Changes
-
f503e9e: Add
HtmlDividercomponent for rendering a themed divider inside MJML ending tags or outside the MJML contextHtmlDividerreadsheight,backgroundColor, andbackgroundImagefromtheme.divider, and supports named variants with per-breakpoint responsive overrides — the same shape astheme.text. Per-instanceheight,backgroundColor, andbackgroundImageprops override the resolved theme/variant values. AbackgroundImage(typically a gradient) overlays the bar whilebackgroundColorstays as the solid fallback for clients that don't render gradients.import { HtmlDivider } from "@comet/mail-react"; <MjmlRaw> <HtmlDivider /> <HtmlDivider variant="thick" /> <HtmlDivider height="2px" backgroundColor="#FF0000" /> <HtmlDivider backgroundImage="linear-gradient(to right, red, blue)" /> </MjmlRaw>;
Example theme
import { createTheme } from "@comet/mail-react"; const theme = createTheme({ divider: { defaultVariant: "thin", variants: { thin: { height: "1px", backgroundColor: "#999999" }, thick: { height: { default: "12px", mobile: "8px" }, backgroundColor: "#222222" }, gradient: { backgroundColor: "#5B4FC7", backgroundImage: "linear-gradient(to right, #5B4FC7, #FF6B6B, #FFD166)", }, }, }, }); declare module "@comet/mail-react" { interface DividerVariants { thin: true; thick: true; gradient: true; } }
-
568ee9a: Add
HtmlImagecomponentRenders an
<img>tag that adapts to its container width below the default breakpoint. Use within raw HTML context — HTML-only emails or MJML ending tags likeMjmlRaw.import { HtmlImage } from "@comet/mail-react"; <HtmlImage src="https://example.com/banner.png" width="600" height="300" alt="Banner" />;
-
1852cfc: Add
HtmlInlineLinkcomponentRenders an
<a>tag that inherits text styles from the surroundingHtmlTextorMjmlTextcomponent, working around Outlook Desktop's built-in "Hyperlink" character style that overrides natural CSS inheritance with blue color and Times New Roman.<MjmlText> Visit our <HtmlInlineLink href="https://example.com">website</HtmlInlineLink> for details. </MjmlText>
-
09e9d03: Add
HtmlTextcomponent for rendering themed text inside MJML ending tags or outside of the MJML contextimport { HtmlText } from "@comet/mail-react"; const MyText = () => ( <MjmlRaw> <table> <tr> <HtmlText variant="heading" bottomSpacing> Heading inside raw HTML </HtmlText> </tr> </table> </MjmlRaw> );
Supports an optional
elementprop to render as any HTML element instead of the default<td>.<HtmlText element="div">Rendered as a div</HtmlText> <HtmlText element="a" href="/link">Rendered as an anchor</HtmlText>
-
0cc1b06: Add config context with
Config,ConfigProvider, anduseConfigConfigis an augmentable interface for runtime configuration — intended for environment-specific data. Add custom keys via TypeScript interface declaration merging:declare module "@comet/mail-react" { interface Config { myKey?: { foo: string }; } }
Define the config using the
configprop onMjmlMailRootor by mountingConfigProviderdirectly. Use theuseConfighook to read the value. -
ba777cf: Add
HtmlPixelImageBlockandMjmlPixelImageBlockfor rendering Comet CMSPixelImageBlockDatain emailsConfigure
MjmlMailRoot.config.pixelImageBlockonce with the API's allowed image sizes and base URL; the blocks resolve the render width and build the image URL.Pass
aspectRatio(e.g."16x9") to override the DAM crop ratio.<MjmlMailRoot config={{ pixelImageBlock: { validSizes: [...cometConfig.images.imageSizes, ...cometConfig.images.deviceSizes], baseUrl: process.env.API_URL, }, }} > <MjmlPixelImageBlock data={pixelImageData} width={536} /> </MjmlMailRoot>
-
a7fb42c: Add
createRichTextBlockfor rendering Comet CMS RichText block data in emailsThe factory returns an
MjmlRichTextBlockfor the MJML context and anHtmlRichTextBlockfor raw-HTML contexts (e.g. insideMjmlRaw), both driven by the same configuration. TheblockTypesoption maps the application's draft block types to theme text variants or plain style values; without it, every block renders with the base theme text styles. ThelinkTypesoption adds href resolvers for the application's link block types on top of the built-inexternalsupport. Theinlineoption overrides the built-in inline styles (BOLD,ITALIC, …) or renders custom inline styles the application defines in its RTE (e.g.HIGHLIGHT).Call the factory once — at the top level of a file, not inside a component — and export the returned components:
export const { MjmlRichTextBlock, HtmlRichTextBlock } = createRichTextBlock({ blockTypes: { "header-one": { variant: "heading1" }, "paragraph-standard": { variant: "body" }, }, });
Usage sites pass only the block data:
<MjmlRichTextBlock data={richTextData} />
Call the factory again for differently-configured blocks, renaming the destructured components — e.g. a headline-only block:
export const { MjmlRichTextBlock: MjmlHeadlineRichTextBlock, HtmlRichTextBlock: HtmlHeadlineRichTextBlock } = createRichTextBlock({ blockTypes: { "header-one": { variant: "heading1" }, "header-two": { variant: "heading2" }, }, });
External links render as
HtmlInlineLink. Lists render flat as<ul>/<ol>. -
6104af6: Add
MjmlMailRootcomponent that provides the standard email skeleton (<Mjml>,<MjmlHead>,<MjmlBody>) with zero-padding defaults -
a7bb900: Add
headandattributesprops toMjmlMailRoot-
head—ReactNodeappended inside<MjmlHead>after the registered styles block. -
attributes—ReactNodeappended inside<MjmlAttributes>after the default<MjmlAll>.<MjmlMailRoot attributes={<MjmlClass name="link" color="blue" />} head={<MjmlFont name="Foo" href="https://example.com/foo.css" />}> {/* email body */} </MjmlMailRoot>
-
-
ea8bbe6: Add
disableResponsiveBehaviorandslotPropsprops toMjmlSection -
1fd6ed9:
MjmlTextnow supportsvariantandbottomSpacingprops, configured throughtheme.text-
theme.textdefines static base styles, e.g., the global default font family -
theme.text.variantsoverrides the base styles, optionally, with responsive style objects where needed -
The
bottomSpacingprop onMjmlTextenables spacing below the text, as defined by thetheme.text.bottomSpacingortheme.text.variants.bottomSpacingtheme values -
MjmlMailRootapplies thefontFamilyfromtheme.textas the mail-wide defaultSimple example theme
import { createTheme } from "@comet/mail-react"; const theme = createTheme({ text: { fontFamily: "Georgia, serif", fontSize: "16px", lineHeight: "24px", bottomSpacing: "12px", }, });
Usage:
import { MjmlMailRoot, MjmlText } from "@comet/mail-react"; <MjmlMailRoot theme={theme}> <MjmlSection> <MjmlColumn> <MjmlText bottomSpacing>Hello</MjmlText> <MjmlText>This is a small paragraph.</MjmlText> </MjmlColumn> </MjmlSection> </MjmlMailRoot>;
Example theme with custom variants
import { createTheme } from "@comet/mail-react"; const theme = createTheme({ text: { fontFamily: "Georgia, serif", fontSize: "16px", lineHeight: "24px", defaultVariant: "body", variants: { heading: { fontSize: { default: "28px", mobile: "22px" }, fontWeight: 700, }, body: { fontSize: "16px", }, }, }, }); declare module "@comet/mail-react" { interface TextVariants { heading: true; body: true; } }
Usage:
import { MjmlMailRoot, MjmlText } from "@comet/mail-react"; <MjmlMailRoot theme={theme}> <MjmlSection> <MjmlColumn> <MjmlText variant="heading" bottomSpacing> Title </MjmlText> <MjmlText bottomSpacing>Body copy uses defaultVariant, which is "body" in this theme.</MjmlText> <MjmlText>This is another paragraph, using the "body" variant.</MjmlText> </MjmlColumn> </MjmlSection> </MjmlMailRoot>;
-
-
ed3b395: Add
registerStylesfor component-level responsive CSSRegister CSS styles at module scope via
registerStyles. Registered styles are automatically rendered as<mj-style>elements in<MjmlHead>byMjmlMailRoot. Styles can be static CSS strings or functions that receive the active theme. -
5e626ca: Add
renderMailHtmlfunction via/serverand/clientsub-path exportsThe new
renderMailHtmlfunction handles the full React → MJML → HTML pipeline in a single call, returning{ html, mjmlWarnings }.
Use@comet/mail-react/serverin Node.js environments and@comet/mail-react/clientin browser environments.Example
// In a Node.js context (e.g. email sending service): import { renderMailHtml } from "@comet/mail-react/server"; const { html, mjmlWarnings } = renderMailHtml(<MyEmail />);
// In a browser context (e.g. Storybook, preview): import { renderMailHtml } from "@comet/mail-react/client"; const { html, mjmlWarnings } = renderMailHtml(<MyEmail />);
-
ed3b395: Add
ResponsiveValue<T>type withgetDefaultFromResponsiveValueandgetResponsiveOverrideshelpersGeneric type for breakpoint-aware theme tokens. A
ResponsiveValueis either a plain value or an object keyed by breakpoint names with a requireddefault. The helpers resolve the default value for inline styles and extract per-breakpoint overrides for media queries.Use this when augmenting the theme with values that should vary per breakpoint, e.g. a custom
titleFontSize: ResponsiveValue. -
ed3b395: Add
indentprop toMjmlSectionfor content indentationMjmlSectionnow accepts an optionalindentboolean prop that applies left/right padding based ontheme.sizes.contentIndentation. The default indentation is applied as inline padding, with responsive overrides via registered media queries. -
2e9b518: Add Storybook addon preset at
@comet/mail-react/storybookConsumers add a single line to
.storybook/main.tsto get a complete mail development setup:const config: StorybookConfig = { addons: ["@comet/mail-react/storybook"], };
The preset auto-registers:
- A mail renderer decorator that wraps stories in
<MjmlMailRoot>and renders them to HTML - A "Copy Mail HTML" toolbar button to copy the rendered email HTML to the clipboard
- A "Use public image URLs" toggle to replace image sources with public placeholder URLs (useful for testing on external services like Email on Acid)
- An "MJML Warnings" panel showing validation warnings with a badge count
- A mail renderer decorator that wraps stories in
-
a0fef0b: Add theme background colors
Add a
colorskey to the theme withbackground.bodyandbackground.contentdefaults.MjmlMailRootnow appliestheme.colors.background.bodyas the body background color, andMjmlSectionappliestheme.colors.background.contentas the default section background when a theme is present. An explicitbackgroundColorprop onMjmlSectionalways takes precedence.Example
const theme = createTheme({ colors: { background: { body: "#EAEAEA", content: "#F8F8F8", }, }, }); <MjmlMailRoot theme={theme}> <MjmlSection>{/* Section gets #F8F8F8 background from theme */}</MjmlSection> <MjmlSection backgroundColor="#FF0000">{/* Explicit prop overrides theme default */}</MjmlSection> </MjmlMailRoot>;
-
59e9904: Add theme system with
createTheme,ThemeProvider, anduseThemecreateThemeproduces aThemewith layout design tokens (sizes.bodyWidth,breakpoints.default,breakpoints.mobile). Pass an overrides object to customize sizes and breakpoints — breakpoint values are constructed withcreateBreakpoint. All theme interfaces support TypeScript module augmentation for project-specific extensions.MjmlMailRootnow accepts an optionalthemeprop. When provided, it sets the email body width and MJML responsive breakpoint from the theme. -
124d889: Add theme support to
MjmlButtonand add a newHtmlButtoncomponentMjmlButtonnow supports theme-based styling throughtheme.button, an optionalvariantprop, and afullWidthprop that makes the button span its container.HtmlButtonprovides the same theming for MJML ending tags and other raw-HTML contexts.-
theme.buttonsets the base button styling (color, background, border, border radius, font, and inner padding);theme.button.variantsoverrides those per named variant, optionally with per-breakpoint responsive values -
theme.button.backgroundImage(typically a gradient) overlaysbackgroundColor, which stays as the solid fallback for clients that don't render gradients (notably Outlook) -
HtmlButtonhas no alignment prop; horizontal placement is handled by the containing cell or layout -
Existing
MjmlButtonusages are unchanged when notheme.buttonis configuredExample theme
import { createTheme } from "@comet/mail-react"; const theme = createTheme({ button: { borderRadius: "6px", padding: "12px 28px", defaultVariant: "primary", variants: { primary: { backgroundColor: "#5B4FC7", color: "#FFFFFF" }, gradient: { backgroundColor: "#5B4FC7", backgroundImage: "linear-gradient(to right, #5B4FC7, #FF6B6B)", color: "#FFFFFF", }, }, }, }); declare module "@comet/mail-react" { interface ButtonVariants { primary: true; gradient: true; } }
Usage:
import { MjmlButton, MjmlMailRoot } from "@comet/mail-react"; <MjmlMailRoot theme={theme}> <MjmlSection> <MjmlColumn> <MjmlButton href="https://example.com">Default</MjmlButton> <MjmlButton href="https://example.com" variant="gradient" fullWidth> Gradient, full width </MjmlButton> </MjmlColumn> </MjmlSection> </MjmlMailRoot>;
-
-
92a475f:
MjmlWrappernow applies the themes content background color by default when used within aThemeProviderorMjmlMailRoot
Patch Changes
-
ffe85a7: Add support for React 17
-
92a475f: Fix
MjmlSectionoverridingMjmlWrapper's backgroundWhen rendered inside a custom
MjmlWrapper,MjmlSectionno longer applies its theme-defaultbackgroundColor, so the wrapper's background is now visible through its sections. An explicitbackgroundColorprop onMjmlSectionstill takes precedence. Sections rendered outside of a wrapper continue to receive the theme default as before. -
b459ec7: Reduce published package size by keeping non-runtime build artifacts out of the bundle
@comet/site-nextjs@9.0.0
Major Changes
-
8b3932d: Move server-only exports to
/serversubpathServer-only exports have been moved to a separate
/serverentry point to prevent server-only code from being pulled into client bundles. While tree-shaking previously removed unused server code, this is an optional optimization — Vite's dev server, for example, does not tree-shake, causing errors when importing these packages in non-server environments (e.g., Storybook).@comet/site-nextjs:sitePreviewRoute,legacyPagesRouterSitePreviewApiHandler,previewParams,legacyPagesRouterPreviewParams, andpersistedQueryRoutemust now be imported from@comet/site-nextjs/server:- import { sitePreviewRoute } from "@comet/site-nextjs"; + import { sitePreviewRoute } from "@comet/site-nextjs/server";
- import { previewParams } from "@comet/site-nextjs"; + import { previewParams } from "@comet/site-nextjs/server";
- import { persistedQueryRoute } from "@comet/site-nextjs"; + import { persistedQueryRoute } from "@comet/site-nextjs/server";
@comet/site-react:persistedQueryRoutemust now be imported from@comet/site-react/server:- import { persistedQueryRoute } from "@comet/site-react"; + import { persistedQueryRoute } from "@comet/site-react/server";
Minor Changes
-
ab5e547: Add
JsonLdcomponent for typed schema.org structured dataRenders any
schema-dtsentity inside a<script type="application/ld+json">tag. The payload is escaped so a</script>sequence in user content cannot break out of the script tag.import { JsonLd } from "@comet/site-react"; import type { Organization } from "schema-dts"; <JsonLd<Organization> data={{ "@context": "https://schema.org", "@type": "Organization", name: "Acme", url: "https://acme.example", logo: "https://acme.example/logo.png", }} />;
Also re-exported from
@comet/site-nextjs. -
54f57dd: Support loading data for child blocks embedded in rich text content in
recursivelyLoadBlockDataChild blocks embedded in a
createTipTapRichTextBlockrich text block (stored ascmsBlock/cmsInlineBlocknodes) are now traversed byrecursivelyLoadBlockData, so their registered block loaders run just like for any other nested block. This allows rich text child blocks to load additional data (e.g. via a GraphQL query) without relying on aBlockTransformerServiceon the API side.To support this, the
tipTapContentfield of acreateTipTapRichTextBlockblock is now declared with a dedicatedTipTapRichTextBlockblock meta kind (instead ofJson) that carries the configured child blocks. The block loader uses this kind to detect rich text content instead of inspecting arbitraryJsonfields.Also re-exported from
@comet/site-nextjs. -
740dba8: Add support for Next.js 15 and 16
@comet/site-nextjsnow supports Next.js 14, 15, and 16. We recommend using Next.js 16. -
740dba8: Add support for React 19
@comet/site-nextjsnow supports React 18 and React 19.
Patch Changes
-
b7daf28: Fix
PixelImageBlockandImagefailing to render in Next.js Pages Router withError: Element type is invalid ... but got: object@comet/site-nextjsis published as ESM ("type": "module") and the components used a default import ofnext/image(CJS). Under Next.js Pages Router the server bundler keeps node_modules ESM packages as Node-style externals, which applies Node-style ESM↔CJS interop:import NextImage from "next/image"yields the entire module-namespace object ({ default, getImageProps, __esModule: true }) instead of the component. The default import is now unwrapped at module evaluation time so the components work under both bundler-style and Node-style interop. -
865fcfd: Remove legacy CJS fields (
module,types) from package.json as these packages are ESM-only
@comet/site-react@9.0.0
Major Changes
-
8b3932d: Move server-only exports to
/serversubpathServer-only exports have been moved to a separate
/serverentry point to prevent server-only code from being pulled into client bundles. While tree-shaking previously removed unused server code, this is an optional optimization — Vite's dev server, for example, does not tree-shake, causing errors when importing these packages in non-server environments (e.g., Storybook).@comet/site-nextjs:sitePreviewRoute,legacyPagesRouterSitePreviewApiHandler,previewParams,legacyPagesRouterPreviewParams, andpersistedQueryRoutemust now be imported from@comet/site-nextjs/server:- import { sitePreviewRoute } from "@comet/site-nextjs"; + import { sitePreviewRoute } from "@comet/site-nextjs/server";
- import { previewParams } from "@comet/site-nextjs"; + import { previewParams } from "@comet/site-nextjs/server";
- import { persistedQueryRoute } from "@comet/site-nextjs"; + import { persistedQueryRoute } from "@comet/site-nextjs/server";
@comet/site-react:persistedQueryRoutemust now be imported from@comet/site-react/server:- import { persistedQueryRoute } from "@comet/site-react"; + import { persistedQueryRoute } from "@comet/site-react/server";
Minor Changes
-
ab5e547: Add
JsonLdcomponent for typed schema.org structured dataRenders any
schema-dtsentity inside a<script type="application/ld+json">tag. The payload is escaped so a</script>sequence in user content cannot break out of the script tag.import { JsonLd } from "@comet/site-react"; import type { Organization } from "schema-dts"; <JsonLd<Organization> data={{ "@context": "https://schema.org", "@type": "Organization", name: "Acme", url: "https://acme.example", logo: "https://acme.example/logo.png", }} />;
Also re-exported from
@comet/site-nextjs. -
4c1aeb2: Add
noFollowoption toExternalLinkBlockEditors can now mark an external link as
nofollowvia a new checkbox in the admin form. When enabled, the rendered<a>tag receivesrel="nofollow". Existing links are unaffected by an automatic block-data migration that setsnoFollowtofalse. -
54f57dd: Support loading data for child blocks embedded in rich text content in
recursivelyLoadBlockDataChild blocks embedded in a
createTipTapRichTextBlockrich text block (stored ascmsBlock/cmsInlineBlocknodes) are now traversed byrecursivelyLoadBlockData, so their registered block loaders run just like for any other nested block. This allows rich text child blocks to load additional data (e.g. via a GraphQL query) without relying on aBlockTransformerServiceon the API side.To support this, the
tipTapContentfield of acreateTipTapRichTextBlockblock is now declared with a dedicatedTipTapRichTextBlockblock meta kind (instead ofJson) that carries the configured child blocks. The block loader uses this kind to detect rich text content instead of inspecting arbitraryJsonfields.Also re-exported from
@comet/site-nextjs. -
740dba8: Add support for React 19
Patch Changes
-
cfa70a2: Fix preview-image to playback transition in video blocks
DamVideoBlock: clicking the preview image's play button now starts video playback. Previously, the click dismissed the preview but the browser's autoplay policy blocked playback of videos with sound because the gesture happened on the preview image rather than the<video>element. Playback is now triggered explicitly inside the ref callback to stay within the user gesture window.YouTubeVideoBlock/VimeoVideoBlock: when the preview image is dismissed,isPlayingis now set totruesoPlayPauseButtonshows the correct icon, and the playback is flagged as manually handled so the viewport handler does not immediately pause the video.
-
4f018d5: Fix
VimeoVideoBlocknot autoplaying on initial page load whenautoplayis enabled and nopreviewImageis setWithout a
previewImage, the iframe URL was missingautoplay=1and playback relied on apostMessage("play")fired from theIntersectionObservercallback. That message raced against the Vimeo player's initialization inside the iframe — when it arrived first the message was dropped and the video stayed paused, while thePlayPauseButtonoptimistically showed the "Pause" state, requiring two clicks to recover.autoplay=1is now appended wheneverautoplayis enabled so Vimeo handles autoplay natively. The existingmuted=1param satisfies the browser autoplay policy.The iframe is also marked with
loading="lazy"so blocks far below the fold don't request the Vimeo player upfront. -
d870d05: Fix Cookiebot consent initialization
Fix a race condition where the
CookiebotOnConsentReadyevent fires before theuseCookieBotCookieApihook is mounted. -
e125c84: Use
OnetrustActiveGroupsinstead ofConsentIntegrationDatainuseOneTrustCookieApiConsentIntegrationDatais used for OneTrust's internal logging and can benull, which causeduseOneTrustCookieApito crash. As recommended by OneTrust support,window.OnetrustActiveGroupsis used instead, as it is always available when the consent banner is implemented. -
865fcfd: Remove legacy CJS fields (
module,types) from package.json as these packages are ESM-only