Skip to content

9.0.0

Choose a tag to compare

@github-actions github-actions released this 07 Jul 12:45
9bbaa3d

@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 -> DatePickerFieldProps

      DateRangePicker:

    • Future_DateRangePicker -> DateRangePicker

    • Future_DateRangePickerProps -> DateRangePickerProps

    • Future_DateRangePickerClassKey -> DateRangePickerClassKey

    • Future_DateRangePickerField -> DateRangePickerField

    • Future_DateRangePickerFieldProps -> DateRangePickerFieldProps

      TimePicker:

    • Future_TimePicker -> TimePicker

    • Future_TimePickerProps -> TimePickerProps

    • Future_TimePickerClassKey -> TimePickerClassKey

    • Future_TimePickerField -> TimePickerField

    • Future_TimePickerFieldProps -> TimePickerFieldProps

      DateTimePicker:

    • Future_DateTimePicker -> DateTimePicker

    • Future_DateTimePickerProps -> DateTimePickerProps

    • Future_DateTimePickerClassKey -> DateTimePickerClassKey

    • Future_DateTimePickerField -> DateTimePickerField

    • Future_DateTimePickerFieldProps -> DateTimePickerFieldProps

      If your theme is using defaultProps or styleOverrides for any of these components, update their component-keys:

    • CometAdminFutureDatePicker -> CometAdminDatePicker

    • CometAdminFutureDateRangePicker -> CometAdminDateRangePicker

    • CometAdminFutureTimePicker -> CometAdminTimePicker

    • CometAdminFutureDateTimePicker -> CometAdminDateTimePicker

      If 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 clearable prop from Autocomplete, FinalFormInput, FinalFormNumberInput and FinalFormSearchTextField

    Those fields are now clearable automatically when not set to required, disabled or readOnly.

  • 3c81ff0: Remove hasClearableContent prop from ClearInputAdornment

    The component now always renders when included in the component tree. Callers should conditionally render the component instead of passing the hasClearableContent prop.

    Migration:

    Before:

    <ClearInputAdornment position="end" hasClearableContent={Boolean(value)} onClick={() => onChange("")} />

    After:

    {
        value && <ClearInputAdornment position="end" onClick={() => onChange("")} />;
    }
  • 631540c: Rename variant prop of Tooltip to color and remove the neutral and primary options

     <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 TimeRangePicker and TimeRangePickerField components

    These MUI X-based components complete the set of date/time components in @comet/admin and replace the deprecated TimeRangePicker, TimeRangeField and FinalFormTimeRangePicker from @comet/admin-date-time.

    The value and the value passed to onChange are a TimeRange object with start and end as 24-hour time strings (HH:mm):

    type TimeRange = {
        start: string | null;
        end: string | null;
    };
  • d7b77af: Add ErrorHandlerProvider and useErrorHandler for centralized error reporting from ErrorBoundary

    ErrorBoundary now invokes an optional onError(error, errorInfo) callback provided through ErrorHandlerProvider's context whenever it catches an error. The visible Alert UI of ErrorBoundary is 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 DependenciesList and DependentsList

    Users 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 GqlFilter type is now exported from @comet/admin.

    Breaking changes:

    @comet/cms-api: DependencyFilter.targetGraphqlObjectType and DependentFilter.rootGraphqlObjectType changed from string to StringFilter. Update any code passing a plain string to use { equal: "..." } instead.

    @comet/cms-api: DependenciesService.getDependents() and getDependencies() consolidated the filter, paginationArgs, and options parameters into a single options object. 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 to DependenciesList and DependentsList must now accept $filter and $sort variables and forward them to the dependencies/dependents field. 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 (supports batchTranslate automatically):

    <AzureAiTranslatorProvider enabled showApplyTranslationDialog>
        {children}
    </AzureAiTranslatorProvider>

    Making a document type translatable

    Add createDocumentTranslationMethods and the TranslatableInterface type 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

    createUsePage now returns a translateContent function. Use it with TranslateContentMenuItem inside a CrudMoreActionsMenu:

    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 FinalFormInput

    Previously, only type="text" fields supported content translation. Now type="textarea" fields (used by TextAreaField) 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 singleSelect columns

    When a singleSelect column uses valueOptions, Excel export now resolves the exported cell value to the matching option label unless a custom valueFormatter overrides it.

  • 57678d0: Fix FinalFormNumberInput dirtying the form on mount when the initial value is null

    The value-sync effect inside FinalFormNumberInput ran on mount and called input.onChange(undefined) whenever input.value was empty. For a form whose initial value was null, this silently normalized the value to undefined, making the field dirty before any user interaction and breaking dirty-tracking features such as EditDialog, SaveBoundary, and the unsaved-changes router prompt.

    The mount-time sync now only updates the formatted display string. The empty-input normalization to undefined still happens on real user interaction in handleBlur.

  • fdabaf1: Fix MainContent with fullHeight growing past the viewport after returning from a scrolled detail page

    useTopOffset used getBoundingClientRect().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 a calc(100vh - topOffset) larger than the viewport. The offset is now measured relative to the document by adding window.scrollY.

  • 8e40458: Fix Stack crash when location changes before any breadcrumb is registered

    Stack accessed the last entry of an empty breadcrumb array on location change, causing a TypeError: 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 DataGridPagination page information not pluralizing "items"

    The default message for comet.dataGridPagination.pageInformation used a fixed plural form, forcing translations to follow the same shape. In languages with distinct singular/plural forms, this produced an incorrect result when itemsTotal === 1. The default message now uses ICU plural syntax so translators can branch on count.

    Projects that maintain their own translation of comet.dataGridPagination.pageInformation should update it to use plural with one and other branches.

@comet/admin-babel-preset@9.0.0

Major Changes

@comet/admin-color-picker@9.0.0

Major Changes

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/admin

    In 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 defaultProps or styleOverrides in the theme:

    • CometAdminDatePicker -> CometAdminLegacyDatePicker

    • CometAdminDateRangePicker -> CometAdminLegacyDateRangePicker

    • CometAdminDateTimePicker -> CometAdminLegacyDateTimePicker

    • CometAdminTimePicker -> CometAdminLegacyTimePicker

  • 15e771b: Deprecate TimeRangePicker, TimeRangeField and FinalFormTimeRangePicker and add a "Legacy" prefix to their class-names and theme component-key

    Their new counterparts in @comet/admin are now considered stable.

    Consider using the new components from @comet/admin

    In 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 defaultProps or styleOverrides in the theme:

    • CometAdminTimeRangePicker -> CometAdminLegacyTimeRangePicker

  • 5f1566a: Make packages ESM-only

Minor Changes

  • 1a83c01: Deprecate the @comet/admin-date-time package

    All of its components now have stable replacements in @comet/admin. The remaining exports (DatePickerNavigation, DateFnsLocaleProvider, DateFnsLocaleContext and useDateFnsLocale) 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 initialPageSize option to GridConfig for the Admin Generator. When set, the value is passed as pageSize to useDataGridRemote in the generated DataGrid code.
  • f066335: Add support for React 19

@comet/admin-icons@9.0.0

Major Changes

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

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 (supports batchTranslate automatically):

    <AzureAiTranslatorProvider enabled showApplyTranslationDialog>
        {children}
    </AzureAiTranslatorProvider>

    Making a document type translatable

    Add createDocumentTranslationMethods and the TranslatableInterface type 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

    createUsePage now returns a translateContent function. Use it with TranslateContentMenuItem inside a CrudMoreActionsMenu:

    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 BrevoContactForm sending sendDoubleOptIn in update mutation

    sendDoubleOptIn is not a valid field of BrevoContactUpdateInput and 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 DependenciesList and DependentsList

    Users 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 GqlFilter type is now exported from @comet/admin.

    Breaking changes:

    @comet/cms-api: DependencyFilter.targetGraphqlObjectType and DependentFilter.rootGraphqlObjectType changed from string to StringFilter. Update any code passing a plain string to use { equal: "..." } instead.

    @comet/cms-api: DependenciesService.getDependents() and getDependencies() consolidated the filter, paginationArgs, and options parameters into a single options object. 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 to DependenciesList and DependentsList must now accept $filter and $sort variables and forward them to the dependencies/dependents field. 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 createHttpClient function

    Use 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 filesInfoText slot from FileSelect

  • 85b09a2: Replace DependencyList with DependenciesList and DependentsList

    Breaking change: DependencyList has been removed. Use DependenciesList for queries returning item.dependencies and DependentsList for queries returning item.dependents.

  • 171c335: Redirects: add domain source type

    To fully support domain redirects, additional handling is required in the site middleware.

Minor Changes

  • 4c1aeb2: Add noFollow option to ExternalLinkBlock

    Editors can now mark an external link as nofollow via a new checkbox in the admin form. When enabled, the rendered <a> tag receives rel="nofollow". Existing links are unaffected by an automatic block-data migration that sets noFollow to false.

  • dc8f29c: Add SitePreviewAction to DocumentInterface

    Allows 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 placeholders option to createTipTapRichTextBlock that 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 allowPageDelete option to disable deletion of pages in the PageTree. When set to false, the delete option is hidden in the Admin UI and the API blocks deletion attempts.

  • d7b77af: Add onError to CometConfig for centralized error reporting from all error boundaries

    CometConfigProvider now accepts an optional onError(error, errorInfo) callback that is invoked whenever any descendant ErrorBoundary catches 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 ChooseDamFilesDialog

    Allows 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 isLinkTarget and validateLinkTarget

  • 127a492: Add TipTapRichTextBlock as an alternative to RichTextBlock

  • c6703db: Add multiple prop to FileField for selecting multiple DAM files

    FileField now accepts multiple={true} to select a list of DAM files instead of a single file. Multi-file values are typed as GQLDamFileFieldFileFragment[] (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 via initialFileIds and 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 FolderDataGrid sortable via column header clicks, using the standard muiGridSortToGql pattern from generated grids. Remove the separate Sort dropdown from the toolbar. Sort state is now stored in URL params instead of localStorage.

  • 7ab96c2: Add SearchHeaderItem component for full-text search

    The SearchHeaderItem component renders a search input (intended for the header) that opens a dropdown with the results of the myFullTextSearch query. The search is restricted to the currently selected content scope. Clicking a result opens the corresponding entity using the entityDependencyMap from the DependenciesConfig (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 createTipTapRichTextBlock

    Child blocks can now be embedded into the TipTap rich text editor, similar to the existing link feature. Configure the supported blocks via the new childBlocks option (both admin and API): a record keyed by a stable key, where each entry is { block, display }. display is 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 (supports batchTranslate automatically):

    <AzureAiTranslatorProvider enabled showApplyTranslationDialog>
        {children}
    </AzureAiTranslatorProvider>

    Making a document type translatable

    Add createDocumentTranslationMethods and the TranslatableInterface type 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

    createUsePage now returns a translateContent function. Use it with TranslateContentMenuItem inside a CrudMoreActionsMenu:

    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 ArchivedTag next to the title on the DAM file detail page

    Previously 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 hideContextMenu not hiding the context menu column in the DAM DataGrid

    The visibility flag was applied to a no-longer-existing contextMenu column id; the column had been renamed to actions. The flag now targets the correct column.

  • 0e9189b: Export AnonymousBlockInterface type

  • fa5c7a4: Fix FileField breaking image block selection

    The DamFileFieldFile fragment lost the image dimensions (width, height, cropArea) needed by DamImageBlock/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 honor eager: true, so each loaded DamFile had an uninitialized image Reference and GraphQL threw Cannot return null for non-nullable field DamFileImage.width. Added an image @ResolveField on FilesResolver that initializes the Reference if needed, so consumers don't have to remember to populate it.

  • 5d006c1: Fix 1-NaN of NaN pagination footer and rowCount warning in DAM FolderDataGrid

    The grid now routes totalCount through useBufferedRowCount, so rowCount stays a number across refetches instead of becoming undefined while data is loading.

  • 31d9296: Fix duplicate TipTap 'link' extension warning by explicitly disabling StarterKit's built-in Link extension

    StarterKit (v3+) includes @tiptap/extension-link by default. Since we register our own CmsLink mark (also named "link"), this caused a "Duplicate extension names found: ['link']" warning. Setting link: false in StarterKit.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 TipTapRichTextBlock toolbar colors to match the existing RichTextBlock toolbar

    The TipTap toolbar incorrectly used Comet's greyPalette (where greyPalette[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-based RichTextBlock toolbar, which uses MUI's lighter grey palette (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 ChooseFileDialog export

    ChooseFileDialog was renamed to ChooseDamFileDialog

  • 5e87236: Remove pagination from StartBuildsDialog data grid

    The buildTemplates query 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 TableBlock RTE cell preview from opening when editing a cell

    Double-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-features package containing skills and rules for AI coding agents

@comet/api-generator@9.0.0

Major Changes

  • 18748d1: Stop generating GraphQL selection-set based populate handling in generated CRUD list resolvers.

    MikroORM dataloader must now be enabled in API projects (for example with dataloader: DataloaderType.ALL in your MikroORM config) to efficiently resolve relation fields.

  • 10dda8c: Remove targetDirectory config from @CrudGenerator decorator 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 @RequiredPermission from entity in resolver permissions

    The UserPermissionsGuard now falls back to the entity's @RequiredPermission decorator 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 @RequiredPermission on generated resolvers if the entity already has @RequiredPermission set. 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_FOUND errors caused by extensionless deep imports of @nestjs/graphql internals. @nestjs/graphql 13.3.0 tightened its exports map so that the "./*": "./*" pattern no longer maps to .js automatically. All deep imports of @nestjs/graphql internals now use explicit .js extensions.

  • 16c0e64: Fix missing import for nested ManyToOne resolver target entities

    @comet/api-generator now imports nested ManyToOne target 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-dependencies rule with devDependencies restriction 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-api to correctly declare @nestjs/graphql, graphql, graphql-scalars, lodash.isequal, and uuid as dependencies/peerDependencies instead of devDependencies, since they are imported in source code.

  • f932845: Update @getbrevo/brevo to v5

    The transactional mail service's send method now accepts a SendTransacEmailRequest (without sender) instead of a SendSmtpEmail. 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-fetch dependency from EcgRtrListService in favor of Node's native fetch

@comet/cms-api@9.0.0

Major Changes

  • 8c2fdde: Add filtering and sorting to DependenciesList and DependentsList

    Users 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 GqlFilter type is now exported from @comet/admin.

    Breaking changes:

    @comet/cms-api: DependencyFilter.targetGraphqlObjectType and DependentFilter.rootGraphqlObjectType changed from string to StringFilter. Update any code passing a plain string to use { equal: "..." } instead.

    @comet/cms-api: DependenciesService.getDependents() and getDependencies() consolidated the filter, paginationArgs, and options parameters into a single options object. 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 to DependenciesList and DependentsList must now accept $filter and $sort variables and forward them to the dependencies/dependents field. 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, and BlobStorageFileStorage are no longer exported from @comet/cms-api as 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-blob or aws-sdk).

  • 3f3da52: Switch to SQL-based entity info system

    The @EntityInfo decorator 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:

    • @EntityInfo decorator API changed: now accepts { name, secondaryInformation?, visible? } with dot-notation field paths, or a raw SQL string
    • EntityInfoServiceInterface has been removed from exports
    • PageTreeNodeDocumentEntityInfoService has been removed; @EntityInfo on Page, Link, and similar document entities is no longer needed
    • block_index_dependencies view exposes two new columns blockVisible and entityVisible; visible is now their logical AND (previously only reflected block-level visibility)
    • block_index_dependencies view now includes rootName, rootSecondaryInformation, targetName, and targetSecondaryInformation columns from EntityInfo, removing the need for a runtime JOIN when querying dependencies/dependents
  • 962a320: Remove importDamFileByDownload mutation

  • 2ea835c: Rename RedirectSourceTypeValues to RedirectSourceType

  • 171c335: Redirects: add domain source type

    To fully support domain redirects, additional handling is required in the site middleware.

Minor Changes

  • f1a473a: Allow AffectedScope to return multiple scopes

    The argsToScope function can now return ContentScope[] in addition to a single ContentScope. 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 (and findUserForLogin / findUserForLoginOrThrow) to UserPermissionsUserServiceInterface

    The throwing getUser and getUserForLogin methods on UserPermissionsUserServiceInterface are 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 define getUser / getUserForLogin continue 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 noFollow option to ExternalLinkBlock

    Editors can now mark an external link as nofollow via a new checkbox in the admin form. When enabled, the rendered <a> tag receives rel="nofollow". Existing links are unaffected by an automatic block-data migration that sets noFollow to false.

  • 67938b2: Filter myFullTextSearch results by the entity's required permission

    The requiredPermission of an entity (declared via the @RequiredPermission decorator) is now included in both the EntityInfo and EntityInfoFullText SQL views.

    The myFullTextSearch query (formerly fullTextSearch) 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. The my prefix 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 placeholders option to createTipTapRichTextBlock that 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 allowPageDelete option to disable deletion of pages in the PageTree. When set to false, 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 recursivelyLoadBlockData

    Child blocks embedded in a createTipTapRichTextBlock rich text block (stored as cmsBlock/cmsInlineBlock nodes) are now traversed by recursivelyLoadBlockData, 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 a BlockTransformerService on the API side.

    To support this, the tipTapContent field of a createTipTapRichTextBlock block is now declared with a dedicated TipTapRichTextBlock block meta kind (instead of Json) that carries the configured child blocks. The block loader uses this kind to detect rich text content instead of inspecting arbitrary Json fields.

    Also re-exported from @comet/site-nextjs.

  • a50793a: Add listFiles method to BlobStorageBackendService

  • 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 urlTemplate field resolver to FileImagesResolver

  • c6703db: Add ids filter to damFilesList

    FileFilterInput now accepts an optional ids: [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 IsLinkTarget validator

  • 127a492: Add TipTapRichTextBlock as an alternative to RichTextBlock

  • 1b37655: Add scope support to myFullTextSearch

    The EntityInfoFullText view now includes a scopes column, and the myFullTextSearch query accepts an optional scope argument to restrict results to a single content scope.

    The scopes are derived from either a scope property on the entity (simple case) or the @ScopedEntity decorator. To support building the scopes in SQL, @ScopedEntity accepts 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 @ScopedEntity is 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 a scope property) — otherwise creating the EntityInfoFullText view throws.

  • 70a77db: Reuse @RequiredPermission from entity in resolver permissions

    The UserPermissionsGuard now falls back to the entity's @RequiredPermission decorator 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 @RequiredPermission on generated resolvers if the entity already has @RequiredPermission set. This avoids redundant permission declarations.

  • 8498a7c: Export EntityInfoObject, EntityInfoFullTextObject and PaginatedEntityInfo

    This 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 entityInfo relation to return name and secondary information.

  • 8ad9dd8: Add support for deleting multiple redirects in the grid

  • bc57b4a: Add support for child blocks in createTipTapRichTextBlock

    Child blocks can now be embedded into the TipTap rich text editor, similar to the existing link feature. Configure the supported blocks via the new childBlocks option (both admin and API): a record keyed by a stable key, where each entry is { block, display }. display is 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 (supports batchTranslate automatically):

    <AzureAiTranslatorProvider enabled showApplyTranslationDialog>
        {children}
    </AzureAiTranslatorProvider>

    Making a document type translatable

    Add createDocumentTranslationMethods and the TranslatableInterface type 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

    createUsePage now returns a translateContent function. Use it with TranslateContentMenuItem inside a CrudMoreActionsMenu:

    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 FileField breaking image block selection

    The DamFileFieldFile fragment lost the image dimensions (width, height, cropArea) needed by DamImageBlock/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 honor eager: true, so each loaded DamFile had an uninitialized image Reference and GraphQL threw Cannot return null for non-nullable field DamFileImage.width. Added an image @ResolveField on FilesResolver that initializes the Reference if needed, so consumers don't have to remember to populate it.

  • f6a2932: Fix MODULE_NOT_FOUND errors caused by extensionless deep imports of @nestjs/graphql internals. @nestjs/graphql 13.3.0 tightened its exports map so that the "./*": "./*" pattern no longer maps to .js automatically. All deep imports of @nestjs/graphql internals now use explicit .js extensions.

  • 35e9e0d: Fix copying the home page creating a second page with the slug home

    Previously, 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 slug home is forbidden). The copy now receives a different slug if the target scope already has a home page, and keeps home only when there is none yet.

  • 91f4a9f: Fix DAM file listing failing for files larger than ~2 GB

    The size field of DamFile and FileUpload was exposed as a GraphQL Int, 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 with Int 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 as BigInt, which represents the full range of file sizes stored in the database (bigint).

  • 6b7adc7: Fix damFilesList returning no files in subfolders when filtering by ids

    damFilesList implicitly constrains to the scope root when no folderId is passed. The constraint already had an escape hatch for filter.searchText; the same now applies to filter.ids. Resolving a selection via filter: { ids: ... } returns all matching files regardless of which folder they live in, which unblocks the admin FileField multi-select for files in subfolders.

  • 31d9296: Fix duplicate TipTap 'link' extension warning by explicitly disabling StarterKit's built-in Link extension

    StarterKit (v3+) includes @tiptap/extension-link by default. Since we register our own CmsLink mark (also named "link"), this caused a "Duplicate extension names found: ['link']" warning. Setting link: false in StarterKit.configure() resolves this.

  • 19a0528: Fix MailerLogStatus GQL enum name (was incorrectly registered as WarningStatus)

  • 71dce06: Support sorting folders by size (child count) in FoldersService

  • f162fa5: Fix AzureOpenAiContentGenerationService for newer GPT models

    We still used the deprecated max_tokens that isn't supported anymore by newer models.
    Replaced it with the newer max_completion_tokens.

  • 1ad7de3: Return null in getNodeByPath when path is /home to prevent the home page from being returned for that path (results in 404)

  • affbb11: Load jsdom lazily for SVG validation

    jsdom (~90 MB resident) was imported and instantiated at module load time, so importing anything from @comet/cms-api pulled 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-node lazily in KubernetesService

    @kubernetes/client-node (~85 MB resident) was statically imported, so importing anything from @comet/cms-api pulled 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 when KubernetesService actually connects to a cluster, reducing the package's base memory footprint.

  • 6793853: Load openai lazily in AzureOpenAiContentGenerationService

    The openai client was statically imported, so importing anything from @comet/cms-api pulled 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.isEqualOrMorePermissions now emits a NestJS Logger.debug line identifying the missing permission or content scope whenever it returns false. The impersonationAllowed resolver field additionally logs when a user attempts to impersonate themselves.

  • 802b0b8: Remove sharp dependency by parsing the dominant color directly from the 1x1 PNG produced by imgproxy

  • 8bf0e5b: Remove unused ts-morph dependency

  • ac59b62: Validate uploaded SVGs with DOMPurify (backed by jsdom) instead of the custom XML-based check

    The previous implementation parsed SVGs with fast-xml-parser and applied a hand-written allow/deny list to reject scripts, event handlers and unsafe links.
    SVGs are now sanitized with the vetted DOMPurify library and rejected when it has to strip any tags or attributes, providing more robust protection against XSS vectors in uploaded SVGs.

    The role attribute 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-type dependency from v16 to v21

@comet/cli@9.0.0

Major Changes

  • 2529907: Replace install-agent-skills with install-agent-features — a combined installer for agent skills and agent rules

    install-agent-features installs skills from skills/<name>/SKILL.md and agentic-plugin/skills/<name>/SKILL.md (folders) and rules from rules/<name>.md (single markdown files) — both from the local repo and from external git repos listed in agent-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 optional metadata.internal: true frontmatter 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-skills command and its agent-skills.json config are removed. Migrate by renaming agent-skills.json to agent-features.json (the schema is identical) and replacing the install-agent-skills invocation in package.json and install.sh with install-agent-features.

Minor Changes

  • 644b4ee: Add node_modules skills and rules discovery to install-agent-features command

    The command now scans direct dependencies in node_modules (including @scoped packages) for skills/ and rules/ 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 from agentic-plugin/skills/

    In addition to the existing skills/ directory, the command now installs skills from agentic-plugin/skills/. This allows shipping agent skills as part of a Claude Code plugin (with a .claude-plugin/plugin.json manifest) without losing the ability to install them via install-agent-skills.

    Both directories are also fetched (via git sparse checkout) when consuming external repos listed in agent-skills.json. skills/ keeps priority over agentic-plugin/skills/.

Patch Changes

  • 560a8f2: Cache getSiteConfigs and op read calls to avoid redundant execution

    When a template contains multiple placeholders for the same environment, getSiteConfigs(env) and op read were called repeatedly with identical arguments. Both are now cached per invocation so each unique env and each unique op:// 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 curly ESLint rule to enforce braces for control statements

    This 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-effects rule

  • f51972c: Enable @graphql-eslint/naming-convention in react.js and nextjs.js

    GraphQL code generation appends the operation kind (Fragment, Query, Mutation, Subscription) to the generated TypeScript type name. Naming an operation FooFragment / FooQuery therefore produces duplicated types like GQLFooFragmentFragment / GQLFooQueryQuery. The @graphql-eslint/naming-convention rule from @graphql-eslint/eslint-plugin reports such names inside gql/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/ packages

    Use no-restricted-imports to 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-dependencies rule with devDependencies restriction 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-api to correctly declare @nestjs/graphql, graphql, graphql-scalars, lodash.isequal, and uuid as dependencies/peerDependencies instead of devDependencies, since they are imported in source code.

  • 695a9c9: Promote future/* ESLint rules into the main configs

    The rules previously only available via @comet/eslint-config/future/* are now part of the main configs and apply by default. The future/* 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-exports
      • formatjs/enforce-default-message is now enforced as "literal"
    • nextjs.js:
      • node-cache is restricted via no-restricted-imports
    • nestjs.js:
      • node-cache is restricted via no-restricted-imports (and restrictedImportPaths is now exported)
  • 740dba8: Add support for Next.js 15 and 16

    @comet/site-nextjs now supports Next.js 14, 15, and 16. We recommend using Next.js 16.

Patch Changes

  • c57e54e: Allow console.info and console.debug in the no-console ESLint 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/ packages

    Use no-restricted-imports to 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: MjmlImage is now responsive by default

    The inner <img> height scales to auto below 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: MjmlDivider now supports variant, height, backgroundColor, and backgroundImage props, configured through theme.divider

    • theme.divider defines the default height and backgroundColor

    • theme.divider.variants overrides those values for named variants, optionally with per-breakpoint responsive values

    • theme.divider.backgroundImage (typically a gradient) overlays the bar while backgroundColor stays as the solid fallback for clients that don't render gradients

    • Per-instance height, backgroundColor, and backgroundImage props override the resolved theme/variant values

      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;
          }
      }

      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 MjmlDivider prop surface no longer accepts borderWidth, borderColor, borderStyle, padding and its variants, width, containerBackgroundColor, align, or cssClass. Migrate borderWidth to height and borderColor to backgroundColor, either per-call or on theme.divider.

    • MjmlDivider no longer applies any default padding around the divider. Add spacing through the surrounding section or column (for example with MjmlSpacer).

    • <MjmlAttributes><MjmlDivider … /></MjmlAttributes> no longer sets defaults for MjmlDivider. Configure defaults through theme.divider instead.

Minor Changes

  • f503e9e: Add HtmlDivider component for rendering a themed divider inside MJML ending tags or outside the MJML context

    HtmlDivider reads height, backgroundColor, and backgroundImage from theme.divider, and supports named variants with per-breakpoint responsive overrides — the same shape as theme.text. Per-instance height, backgroundColor, and backgroundImage props override the resolved theme/variant values. A backgroundImage (typically a gradient) overlays the bar while backgroundColor stays 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 HtmlImage component

    Renders 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 like MjmlRaw.

    import { HtmlImage } from "@comet/mail-react";
    
    <HtmlImage src="https://example.com/banner.png" width="600" height="300" alt="Banner" />;
  • 1852cfc: Add HtmlInlineLink component

    Renders an <a> tag that inherits text styles from the surrounding HtmlText or MjmlText component, 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 HtmlText component for rendering themed text inside MJML ending tags or outside of the MJML context

    import { 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 element prop 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, and useConfig

    Config is 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 config prop on MjmlMailRoot or by mounting ConfigProvider directly. Use the useConfig hook to read the value.

  • ba777cf: Add HtmlPixelImageBlock and MjmlPixelImageBlock for rendering Comet CMS PixelImageBlockData in emails

    Configure MjmlMailRoot.config.pixelImageBlock once 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 createRichTextBlock for rendering Comet CMS RichText block data in emails

    The factory returns an MjmlRichTextBlock for the MJML context and an HtmlRichTextBlock for raw-HTML contexts (e.g. inside MjmlRaw), both driven by the same configuration. The blockTypes option 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. The linkTypes option adds href resolvers for the application's link block types on top of the built-in external support. The inline option 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 MjmlMailRoot component that provides the standard email skeleton (<Mjml>, <MjmlHead>, <MjmlBody>) with zero-padding defaults

  • a7bb900: Add head and attributes props to MjmlMailRoot

    • headReactNode appended inside <MjmlHead> after the registered styles block.

    • attributesReactNode appended 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 disableResponsiveBehavior and slotProps props to MjmlSection

  • 1fd6ed9: MjmlText now supports variant and bottomSpacing props, configured through theme.text

    • theme.text defines static base styles, e.g., the global default font family

    • theme.text.variants overrides the base styles, optionally, with responsive style objects where needed

    • The bottomSpacing prop on MjmlText enables spacing below the text, as defined by the theme.text.bottomSpacing or theme.text.variants.bottomSpacing theme values

    • MjmlMailRoot applies the fontFamily from theme.text as the mail-wide default

      Simple 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 registerStyles for component-level responsive CSS

    Register CSS styles at module scope via registerStyles. Registered styles are automatically rendered as <mj-style> elements in <MjmlHead> by MjmlMailRoot. Styles can be static CSS strings or functions that receive the active theme.

  • 5e626ca: Add renderMailHtml function via /server and /client sub-path exports

    The new renderMailHtml function handles the full React → MJML → HTML pipeline in a single call, returning { html, mjmlWarnings }.
    Use @comet/mail-react/server in Node.js environments and @comet/mail-react/client in 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 with getDefaultFromResponsiveValue and getResponsiveOverrides helpers

    Generic type for breakpoint-aware theme tokens. A ResponsiveValue is either a plain value or an object keyed by breakpoint names with a required default. 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 indent prop to MjmlSection for content indentation

    MjmlSection now accepts an optional indent boolean prop that applies left/right padding based on theme.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/storybook

    Consumers add a single line to .storybook/main.ts to 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
  • a0fef0b: Add theme background colors

    Add a colors key to the theme with background.body and background.content defaults. MjmlMailRoot now applies theme.colors.background.body as the body background color, and MjmlSection applies theme.colors.background.content as the default section background when a theme is present. An explicit backgroundColor prop on MjmlSection always 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, and useTheme

    createTheme produces a Theme with layout design tokens (sizes.bodyWidth, breakpoints.default, breakpoints.mobile). Pass an overrides object to customize sizes and breakpoints — breakpoint values are constructed with createBreakpoint. All theme interfaces support TypeScript module augmentation for project-specific extensions.

    MjmlMailRoot now accepts an optional theme prop. When provided, it sets the email body width and MJML responsive breakpoint from the theme.

  • 124d889: Add theme support to MjmlButton and add a new HtmlButton component

    MjmlButton now supports theme-based styling through theme.button, an optional variant prop, and a fullWidth prop that makes the button span its container. HtmlButton provides the same theming for MJML ending tags and other raw-HTML contexts.

    • theme.button sets the base button styling (color, background, border, border radius, font, and inner padding); theme.button.variants overrides those per named variant, optionally with per-breakpoint responsive values

    • theme.button.backgroundImage (typically a gradient) overlays backgroundColor, which stays as the solid fallback for clients that don't render gradients (notably Outlook)

    • HtmlButton has no alignment prop; horizontal placement is handled by the containing cell or layout

    • Existing MjmlButton usages are unchanged when no theme.button is configured

      Example 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: MjmlWrapper now applies the themes content background color by default when used within a ThemeProvider or MjmlMailRoot

Patch Changes

  • ffe85a7: Add support for React 17

  • 92a475f: Fix MjmlSection overriding MjmlWrapper's background

    When rendered inside a custom MjmlWrapper, MjmlSection no longer applies its theme-default backgroundColor, so the wrapper's background is now visible through its sections. An explicit backgroundColor prop on MjmlSection still 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 /server subpath

    Server-only exports have been moved to a separate /server entry 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, and persistedQueryRoute must 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: persistedQueryRoute must 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 JsonLd component for typed schema.org structured data

    Renders any schema-dts entity 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 recursivelyLoadBlockData

    Child blocks embedded in a createTipTapRichTextBlock rich text block (stored as cmsBlock/cmsInlineBlock nodes) are now traversed by recursivelyLoadBlockData, 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 a BlockTransformerService on the API side.

    To support this, the tipTapContent field of a createTipTapRichTextBlock block is now declared with a dedicated TipTapRichTextBlock block meta kind (instead of Json) that carries the configured child blocks. The block loader uses this kind to detect rich text content instead of inspecting arbitrary Json fields.

    Also re-exported from @comet/site-nextjs.

  • 740dba8: Add support for Next.js 15 and 16

    @comet/site-nextjs now supports Next.js 14, 15, and 16. We recommend using Next.js 16.

  • 740dba8: Add support for React 19

    @comet/site-nextjs now supports React 18 and React 19.

Patch Changes

  • b7daf28: Fix PixelImageBlock and Image failing to render in Next.js Pages Router with Error: Element type is invalid ... but got: object

    @comet/site-nextjs is published as ESM ("type": "module") and the components used a default import of next/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 /server subpath

    Server-only exports have been moved to a separate /server entry 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, and persistedQueryRoute must 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: persistedQueryRoute must 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 JsonLd component for typed schema.org structured data

    Renders any schema-dts entity 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 noFollow option to ExternalLinkBlock

    Editors can now mark an external link as nofollow via a new checkbox in the admin form. When enabled, the rendered <a> tag receives rel="nofollow". Existing links are unaffected by an automatic block-data migration that sets noFollow to false.

  • 54f57dd: Support loading data for child blocks embedded in rich text content in recursivelyLoadBlockData

    Child blocks embedded in a createTipTapRichTextBlock rich text block (stored as cmsBlock/cmsInlineBlock nodes) are now traversed by recursivelyLoadBlockData, 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 a BlockTransformerService on the API side.

    To support this, the tipTapContent field of a createTipTapRichTextBlock block is now declared with a dedicated TipTapRichTextBlock block meta kind (instead of Json) that carries the configured child blocks. The block loader uses this kind to detect rich text content instead of inspecting arbitrary Json fields.

    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, isPlaying is now set to true so PlayPauseButton shows the correct icon, and the playback is flagged as manually handled so the viewport handler does not immediately pause the video.
  • 4f018d5: Fix VimeoVideoBlock not autoplaying on initial page load when autoplay is enabled and no previewImage is set

    Without a previewImage, the iframe URL was missing autoplay=1 and playback relied on a postMessage("play") fired from the IntersectionObserver callback. 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 the PlayPauseButton optimistically showed the "Pause" state, requiring two clicks to recover. autoplay=1 is now appended whenever autoplay is enabled so Vimeo handles autoplay natively. The existing muted=1 param 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 CookiebotOnConsentReady event fires before the useCookieBotCookieApi hook is mounted.

  • e125c84: Use OnetrustActiveGroups instead of ConsentIntegrationData in useOneTrustCookieApi

    ConsentIntegrationData is used for OneTrust's internal logging and can be null, which caused useOneTrustCookieApi to crash. As recommended by OneTrust support, window.OnetrustActiveGroups is 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