Blog

Release announcements, helpful tips, and community discussion

10.0

Cerb (10.0) is a platform upgrade released on April 27, 2021. It includes more than 216 new features and improvements from community feedback.

To check if you qualify for this release as a free update, view Setup » Configure » License. If your software updates expire on or after September 30, 2020 then you can upgrade without renewing your license. Cerb Cloud subscribers will be upgraded automatically.

Important Release Notes

Automations

Automations are state machines written in KATA that transform an input dictionary into an output dictionary. The results are used to automate and customize workflows throughout Cerb.

Interactions

Interactions are interactive automations that use continuations to pause and resume a multi-step workflow.

Toolbars

Toolbars add custom interaction shortcuts throughout the interface.

Resources

Resources are shared assets used by automations and widgets.

Maps

Cerb can create interactive map visualizations with geospatial data from any source using the standard GeoJSON format.

Sheets

See: Sheets

Sheet row selection

Sheets now include a selection: column type. This is used by widgets and interactions to use sheets as a prompt for selecting one or more records. The selection key can be any arbitrary value; or even a compound key, or JSON object.

For instance, on a ticket profile, a sheet widget can display the current ‘Participants’, and rows can be selected in combination with a ‘Remove’ interaction.

Over time this will replace the “choosers” feature. Unlike choosers, sheets aren’t limited to displaying records, and can display any data within a dictionary (e.g. hard-coded options, dynamic API results, etc).

Interaction toolbars on sheets

On card, profile, and workspace sheet widgets, an interaction-powered toolbar may now be included with buttons that change depending on the selected rows.

New rows can be added to a sheet from its toolbar. [#959]

Grid layout

Implemented a new sheet layout:style: of grid. This displays a compact grid rather than a table. For instance, this is used when selecting an icon in some interactions.

Sheet filtering

Added a new layout:filtering@bool: option to sheets. When enabled, this shows a search box above the list to filter the current results.

Project Boards

See: Project Boards

Card content from automations

The content of cards is determined by an automation on the projectBoard.renderCard trigger, which can be provided per-columm, or globally (default for all columns) on the board itself. The automation must return a sheet schema to display.

Column toolbars

Project board columns can now display a toolbar of custom interactions (e.g. add task), and may provide one or more automations on the projectBoard.cardAction trigger (in event handler KATA) to run when a card transitions to a new column.

Default automations are provided for adding tasks, rendering task cards, and rendering completed tasks.

Existing card action behaviors are deprecated and are migrated to the event handler KATA.

Data Queries

Added new data query types:

Type Description
platform.extensions Get a filterable/pageable list of plugin extensions for a given point.
record.field Get a filterable/pageable list of fields on a given record type.
record.types Get a filterable/pageable list of record type.
ui.icons Get a filterable/pageable list of the 600+ built-in icons.

Records

Attachments

  • In attachment record dictionaries, added a download_url placeholder for the download link.

Connected Accounts

  • On connected account records, added a uri field. This is a unique identifier (letters, numbers, dashes) used by other functionality (like automations) to refer to a connected account without using internal IDs.

Connected Services

  • Added an ‘API Key’ connected service. This securely stores an API key, and authorizes HTTP requests by including the key in a named HTTP header or query argument. A ‘base URL’ option ensures the key is only included on requests to an expected HTTP endpoint.

Custom Fields

  • Custom field records must now define a unique nickname (URI). This is used by functionality like the API, queries, and records API to show human-readable custom field names, where previously they were unhelpfully named using their IDs, like custom_123. Existing custom fields are automatically assigned URIs by the update.

  • When expanding record dictionaries, the custom expansion key continues to return ID-based field names, like custom_123. The new customfields expansion key returns URI-based custom field names for human readability.

Reminders

  • Reminder records now use event handler KATA to trigger any number of automations when the alert timer has elapsed. For instance, the reminder worker receive a notification, email, SMS message, etc.

Tickets

  • On ticket profiles, the ‘Participants’ widget now provides shortcut interactions for ‘add’ and ‘remove’.

Webhooks

  • Refactored webhook listeners to use automations rather than behaviors. Automations on the webhook.respond trigger can be provided in event handler KATA. The input includes details about the HTTP request, and output controls the HTTP response. Webhook listener extensions have been retired (behavior was the only option). Existing webhook behaviors are deprecated and are automatically migrated to the new event handler KATA.

Platform

PHP8

Cerb is now compatible with PHP 8.0.

Installer

  • When installing a new instance of Cerb, a snapshot is now used to initialize the database rather than running all of the patches since 4.0. This is much faster, which improves the user experience, as well as speeding up automated tests, Docker images, etc. [#109]

Deprecated and retired features

Deprecated chat behaviors

These extensions can be reimplemented using interaction toolbars:

  • Bot chat behaviors on the “global” interaction point (the round floating Cerb icon the lower right) are now moved into a ‘(Legacy Chat Bots)’ sub-menu. These workflows should be migrated to interaction automations in the cerb.toolbar.global.menu toolbar.

  • Retired bot interaction menus on record cards and profiles.

Deprecated plugin extensions

  • The Sphinx search extension is deprecated and will be removed in v11.0.

Retired plugins

These plugins can be reimplemented using modern functionality:

  • Retired the ‘Sensors’ plugin. An improved version of this functionality is now possible through custom records and automations. The legacy plugin is available at: https://github.com/cerb-plugins/cerberusweb.datacenter.sensors/

  • Retired the ‘Ticket Profile Move To Shortcut’ plugin.

  • Retired the ‘Legacy Printing’ plugin.

  • Retired the ‘Legacy Profile Attachments ZIP Download’ plugin.

Retired plugin extensions

These extensions can be reimplemented using interaction toolbars:

  • Retired message toolbar extensions.

  • Retired ‘Profile Script’ extensions in plugins. These were used to modify profile pages, which can be done with toolbars now.

Retired packages

These packages can be reimplemented using interaction toolbars:

  • Retired the ‘Bot Action: Execute jQuery Script’ package.

  • Retired the ‘Bot Behavior Action: Start Conversation’ package.

  • Retired the ‘Bot Form Interaction Template’ package.

  • Retired the ‘Bot Interaction Template’ package.

  • Retired the ‘Profile Widget: Ticket Reply Draft Generator’ package.

  • Retired the ‘Reminder Bot’ package.

Retired bot behaviors

These behaviors can be reimplemented using interaction toolbars:

  • Retired the ‘Before composing a message reply’ (event.mail.reply.pre.ui.worker) behavior event.

  • Retired the ‘Before composing a new message’ (event.mail.compose.pre.ui.worker) behavior event.

  • Retired the ‘During a message reply’ (event.mail.reply.during.ui.worker) behavior event.

  • Retired the ‘Record editor opened by worker’ (event.ui.card.editor.opened.worker) behavior event.

  • Retired the ‘Respond to Ajax HTTP request’ (event.ajax.request) behavior event. This was triggered by widgets on the /ui/behavior endpoint.

Full changelog

  • [Records/Tickets] On ticket records, added fields for num_messages_in and num_messages_out. These are used for reporting (e.g. “average outgoing messages per ticket”) where it’s much simpler and more efficient to use the precomputed values than calculating them. These can be used as search filters and subtotals with messages.count.in and messages.count.out.

  • [Records/Tickets] On ticket records, the ‘# Messages’, ‘# Messages In’, and ‘# Messages Out’ fields are now available in ‘Record Fields’ widgets.

  • [Data Queries/Subtotals] On worklist.subtotals data queries, a timezone: option now generates date labels in the given timezone offset for bins like by:[created@day]. Previously these labels were generated using the database server’s timezone (e.g. SYSTEM, UTC), which could cause confusion. An option like timezone:"-07:00" uses the offset UTC-7.

  • [Developers/Plugins] Retired reply toolbar extensions (cerberusweb.reply.toolbaritem). These are better handled with bot interactions.

  • [Packages/Tickets] On ticket profiles, the ‘Participants’ package now uses a sheet widget to display a list of participants. Previously this was a custom HTML widget which lacked paging, formatting, etc.

  • [Support Center] Marked the Support Center plugin as deprecated. This is being replaced by the Portal Builder.

  • [Code Editors/Autocompletion/YAML] Fixed an issue in the getYamlTokenPath() JS helper. This wasn’t ignoring text blocks when looking for key tags. Key tokens always begin at column 0, so tokens that begin with other indents are now ignored, even if they match the key: format.

  • [Sheets/UX] Sheet widgets no longer display the paging progress if there’s only a single page (e.g. “Showing 1-3 of 3”).

  • [Automations/Datastores] Implemented a key/value datastore service for automations. This allows for shared keys (permitted by policies) and supports key expiration.

  • [Automations] Implemented “automations” as the declarative successor to bot behaviors. Automations are written in KATA, and an interactive debugger improves the development process. Each has a unique name referenced by other functionality, and is related to a specific “trigger” event. Unlike behaviors, automations aren’t grouped into bots, and they don’t need to exist in the database before being used. This makes automations far more reusable. [#1000]

  • [Automations/Policies] The permissions of automations are governed by “policies”. A policy is a collection of rules for each action which describe the conditions where the action would be permitted. Any action not explicitly permitted is denied by default.

  • [Platform/Javascript/Choosers] Cleaned up the .cerbChooserTrigger() method in the Javascript UI library. This had several overlapping variable scopes.

  • [Records/Email Signatures] Removed the is_default field from email signature records. The default signature is determined by the default group.

  • [Dictionaries] Dictionaries now reconstitute sub-dictionaries when resuming. This supports continued key expansion after multiple suspend/resume cycles.

  • [Mail/Parser] Changed the stripHTML functionality to use DOM rather than regular expressions.

  • [Dictionaries] Implemented iterators for dictionaries.

  • [Dictionaries] Added a setPush method to dictionaries to add elements to the end of an array value in a key path.

  • [Platform/Validation] Added the ‘boolean’ data type to the validation service.

  • [Platform/Validation] Added the ‘date’ text type to the validation service.

  • [Platform/Validation] Added the ‘ip’ text type to the validation service.

  • [Platform/Validation] Added the ‘uri’ text type to the validation service.

  • [Platform/Validation] Added the ‘float’ data type to the validation service.

  • [Platform/Validation] In the validation service, improved the ‘not empty’ check for non-string data types.

  • [Dictionaries] In dictionaries, methods that take a path may now specify an optional delimiter to use (e.g. . or :). This allows potential/default delimiters to occur in keys.

  • [Platform/Javascript] Implemented a getYamlRowByPath() Javascript helper for determining the line of a YAML document by a given path. This is used by the automation editor for step-based debugging.

  • [Platform/Toolbars] Implemented .cerbToolbar UI control in the Javascript library. This enhances a DIV with a toolbar that can contain buttons and nested menus of interactions and functions. Callbacks for done, reset, and error allow the caller to respond to events. The toolbar listens for a cerb-toolbar--refreshed event if the contents dynamically change.

  • [Database/Inclusiveness] Removed the potentially offensive $db->ExecuteSlave() methods in favor of $db->QueryReader().

  • [Data Queries/UI/Icons] Implemented ui.icons data queries for filtering and paging the list of 600+ available icons. See: ui.icons

  • [Data Queries/Records] Implemented record.types data queries for filtering and paging the list of record types. See: record.types

  • [KATA] In KATA, when using the @raw annotation, any subsequent annotations are preserved on the key.

  • [Records/Connected Services] In connected service dictionaries, the extension_name key includes the human-friendly name of the extension.

  • [Automations/Platform] Implemented the automations service responsible for validating and executing scripts. KATA scripts are converted into an abstract syntax tree (AST), which can be cached for improved performance. The service validates inputs: on automations that support them. The result of an execution is a dictionary with a state (return, await, error, exit).

  • [Devblocks/Validation] In the validation service, added a sanity check to the length_min, length_max, and possible_values options when using stringOrArray fields.

  • [Automations/Executions] On automations that suspend in the await state, an “automation execution” record is created to preserve the current state. A unique randomized key is returned to resume the state.

  • [Tests/Automations] Added a test suite for automations.

  • [Platform/Services/Data] In the data query service, added a helper to extract fields from the query. This is used to introspect the type: in validation, automation policies, etc.

  • [Records/Notifications] In notification records, the activity_point is now required. The context and context_id fields are not. The event_json field is hidden so params can be used instead.

  • [Services/Validation] In the validation service, added a stringUpper formatter. This is used when comparing HTTP verbs/methods.

  • [Data Queries/Worklists] Abstracted the paging statistics in worklist.records data queries.

  • [Records/Profiles] Fixed an issue on profile record editors when using ‘Save & Continue’. This was still refreshing the entire profile page.

  • [Dictionaries] In dictionaries, the setPush() method may optionally specify a delimiter for key paths.

  • [Automations/Editor] Implemented an editor for automations. This includes syntax highlighting and autocompletion for the KATA syntax, a step-based debugger with full access to the current state, a simulator, and a reference for each trigger event. A contextual toolbar provides interactions for adding inputs, controls, actions, and return values.

  • [Automations/Editor/Visualization] The automation editor now has a ‘Visualization’ tab with a flowchart for the current script. Clicking on a node highlights the relevant line of code in the editor. Exit states are colorized.

  • [Automations] Implemented AbstractAction superclass for automation action nodes.

  • [Automations/Actions] Implemented the data_query: action in automations. This takes a query: as input and returns the results into the placeholder specified by output:.

  • [Automations/Actions] Implemented the error: action in automations. This stops the automation execution with an ‘error’ exit code and returns the given error message.

  • [Automations/Actions] Implemented the return: action in automations. This stops the automation execution with a ‘return’ exit code and returns the given key/values.

  • [Automations/Actions] Implemented the set: action in automations. This sets multiple placeholders using the given key/value pairs. This is a shortcut for var:action:set.

  • [Automations/Actions] Implemented the yield: action in automations. This pauses the automation execution in the ‘yield’ state and returns the given key/value pairs.

  • [Automations/Actions] Implemented the var: action in automations. This has actions for set, push, and unset.

  • [Automations/Actions] Implemented the storage: action in automations for persisting arbitrary with a key. It has actions for get, set, and delete. The policy determines which storage keys the automation can access.

  • [Automations/Actions] Implemented the function: action in automations for running arbitrary automation.function automations. These accept arbitrary inputs: and return results in the given output: placeholder.

  • [Automations/Actions] Implemented the record: action in automations for managing records. This has actions for: create, get, update, upsert, and delete.

  • [Automations/Triggers] Implemented Extension_AutomationTrigger extensions for adding new triggers to automations.

  • [Data] In the data service, implemented generatePaging() for creating paging statistics for arbitrary data sets.

  • [KATA] When parsing a KATA script, we now keep track of line numbers from the source. This is used to provide better editor integration and error tracing.

  • [Sheets] Sheet ‘search’ columns now display inline rather than block.

  • [Automations/Events] Implemented an “event handler” service for automations. In functionality that invokes automations (e.g. widgets), handlers are described in KATA. For events that expect a single handler (e.g. ui.interaction), the first matching (non-disabled) automation is executed and its end state is returned. This can be used to conditionally respond based on the event/caller. For events that run all handlers (e.g. projectBoard.cardAction), all non-disabled automations are executed in order and their end states are returned.

  • [Profiles/Interaction] Updated profile ‘interaction toolbar’ widgets to use .cerbToolbar.

  • [Workspaces/Interaction] Updated workspace ‘interaction toolbar’ widgets to use .cerbToolbar.

  • [Cards/Interaction] Updated card ‘interaction toolbar’ widgets to use .cerbToolbar.

  • [Sheets] Abstracted sheet rendering and paging to simplify reuse between sheet widgets, automation prompts, etc.

  • [UI/Toolbars/Javascript] In the Javascript UI library, .cerbToolbar() now automatically handles closing menus after interaction or function clicks.

  • [UI/Toolbars] Added a badge option to .cerbToolbar().

  • [Records/Custom Fields] Custom field records must now define a unique nickname (URI). This is used by functionality like the API, queries, and records API to show human-readable custom field names, where previously they were unhelpfully named using their IDs, like custom_123. Existing custom fields are automatically assigned URIs by the update.

  • [Records/Custom Fields] When expanding record dictionaries, the custom expansion key continues to return ID-based field names, like custom_123. The new customfields expansion key returns URI-based custom field names for human readability.

  • [Automations/Triggers] Implemented the ui.interaction trigger for automations. This opens an interactive popup based on a UI element (e.g. from a toolbar button). Interactions may enter the await state any number of times for multi-step workflows where user input is required (e.g. a form). At conclusion, the return dictionary is returned to the caller. For instance, an interaction that generates a text snippet for pasting into an editor at the cursor. Interactions receive a unique caller_name and caller_params for personalizing functionality based on the use case.

  • [Automations/Triggers] Implemented the reminder.remind trigger for automations. This is triggered by a reminder record that reaches its “remind at” date. The input is the reminder_ dictionary.

  • [Automations/Triggers] Implemented the webhook.respond trigger for automations. This is triggered by a webhook listener that receives an HTTP request. The input is the request body, client IP, headers, method, params, and path. The output is the HTTP status code, headers, and body.

  • [Automations/Triggers] Implemented the projectBoard.renderCard trigger for automations. This is triggered when a card is displayed on a project board. The input is dictionaries for the board_*, card_*, and current worker_*. This uses event handler KATA, so the first matching automation is used. For instance, the match can be based on the record type of the card. The output is sheet KATA describing the card’s layout.

  • [Sheets/Selection] Sheets now include a selection: column type. This is used by widgets and interactions to use sheets as a prompt for selecting one or more records. Over time this will replace the “choosers” feature. Unlike choosers, sheets aren’t limited to displaying records, and can display any data within a dictionary (e.g. hard-coded options, dynamic API results, etc). The selection key can be any arbitrary value; or even a compound key, or JSON object. For instance, on a ticket profile, a sheet widget can display the current ‘Participants’, and rows can be selected in combination with a ‘Remove’ interaction.

  • [Automations/Editor] In the automation editor, the ‘Trigger:’ is now selected using an interaction rather than a dropdown.

  • [Platform/Validation] In the validation service, required fields are now always checked. Previously record editors provided blank values, but automations and the records API don’t.

  • [Workspaces/Widgets/Sheets] On workspace sheet widgets, an interaction-powered toolbar may now be included with buttons that change depending on the selected rows.

  • [Cards/Widgets/Sheets] On card sheet widgets, an interaction-powered toolbar may now be included with buttons that change depending on the selected rows.

  • [Profiles/Widgets/Sheets] On profile sheet widgets, an interaction-powered toolbar may now be included with buttons that change depending on the selected rows.

  • [Reminders/Automations] Reminder records now use the ‘event handler’ KATA to trigger any number of automations when the alert timer has elapsed. For instance, the reminder worker receive a notification, email, SMS message, etc.

  • [Automations/Actions] Implemented the http.request: action in automations.

  • [Automations/Actions] Implemented the email.parse: action in automations.

  • [Platform/Classloader] In plugin.xml manifests, it’s now possible to classload PSR-4 paths rather than individual files.

  • [Interactions] Interactions (automations/behaviors) return their final state dictionary to Javascript triggers. For instance, an interaction can insert text into an editor.

  • [Sheets/Grid] On sheets, implemented a new layout:style: of “grid”. This displays records in rows and columns, rather than as a table. For instance, this is used when selecting an icon in some interactions.

  • [Sheets/Filtering] On sheets, added a new layout:filtering@bool: option. When enabled, this shows a search box above the sheet. Various features use this to filter the list.

  • [Automations/Interactions] In ui.interaction automations, added an editor: prompt to the await:form:elements: state. This displays a code editor with syntax highlighting for cerb queries, HTML, JSON, Markdown, text, and YAML.

  • [Automations/Interactions] In ui.interaction automations, added an end: prompt to the await:form:elements: state. This is automatically triggered when an interaction is finished.

  • [Automations/Interactions] In ui.interaction automations, added a map: prompt to the await:form:elements: state. This displays an interactive map using map KATA and returns the selection region(s) or point(s). For instance, an interaction that displays the geographical location of an IP address can drop a pin on a map prompt.

  • [Automations/Interactions] In ui.interaction automations, added a say: prompt to the await:form:elements: state. This displays a block of text or Markdown.

  • [Automations/Interactions] In ui.interaction automations, added a sheet: prompt to the await:form:elements: state. This displays a sheet schema using static or dynamic data. Selection can be set to ‘single’ or ‘multiple’.

  • [Automations/Interactions] In ui.interaction automations, added a submit: prompt to the await:form:elements: state. This displays the ‘Continue’ and ‘Reset’ buttons. It is automatically added when an interaction exits in the ‘await’ state.

  • [Automations/Interactions] In ui.interaction automations, added a text: prompt to the await:form:elements: state. This displays a text input field with various validation options (e.g. date, decimal, bool, email, ip, geopoint, number, record type, timestamp, uri, url).

  • [Data Queries/Records] Implemented record.fields data queries for filtering and paging the available fields for a given record type. See: record.field

  • [Webhooks/Automations] Refactored webhook listeners to use automations rather than behaviors. Automations on the webhook.respond trigger can be provided in event handler KATA. The input includes details about the HTTP request, and output controls the HTTP response. Webhook listener extensions have been retired (behavior was the only option). Existing webhook behaviors are deprecated and are automatically migrated to the new event handler KATA.

  • [Bots/Interactions/Convo] Conversational bot behaviors on the “global” interaction point (the round floating Cerb icon the lower right) are now moved into a ‘(Legacy Chat Bots)’ sub-menu. These workflows should be migrated to automations.

  • [Maps/Automations] Migrated map widgets to automations. This is later refactored to map KATA.

  • [Automations/Triggers] Implemented the projectBoard.cardAction trigger for automations. This is triggered when a card enters a new board column, either through the UI or procedurally. The input is dictionaries for the board_*, card_*, column_*, and current worker_*. This uses event handle KATA, and all enabled automations are used.

  • [Automations/Editor] Added a cerb_kata syntax mode to Ace editor.

  • [Validation] Added an array data type to the validation service.

  • [Project Boards/Automations] Refactored project boards to use automations. The content of cards is determined by an automation on the projectBoard.renderCard trigger, which can be provided per-columm, or globally (default for all columns) on the board itself. The automation must return a sheet schema to display. Project board columns can now display a toolbar of custom interactions (e.g. add task), and may provide one or more automations on the projectBoard.cardAction trigger (in event handler KATA) to run when a card transitions to a new column. Existing card action behaviors are deprecated and are migrated to the event handler KATA. Default automations are provided for adding tasks, rendering task cards, and rendering completed tasks. The ‘Kanban’ project board library package has been updated to demonstrate the new functionality.

  • [Installer/Packages] Updated the installer packages for compatibility with 10.0.

  • [Installer/UX] When installing a new instance of Cerb, a snapshot is now used to initialize the database rather than running all of the patches since 4.0. This is much faster, which improves the user experience, as well as speeding up automated tests, Docker images, etc. Fixes #109

  • [Automations/KATA] Automation, event handler, and toolbar editors now use the KATA syntax mode.

  • [Automations/KATA/URI] Event handler and toolbar KATA now refer to automations or behaviors using a new uri: format. This allows tokens to be displayed as links with card popups, making development much easier. It also allows variable input, like a behavior or an automation, without requiring different node types.

  • [Packages] Improved error reporting in packages to disambiguate issues in validation, generation, or importing.

  • [Automations] Renamed internal (non-reusable) automations so they can be excluded from choosers in interactions.

  • [Automations/Triggers] Implemented the ui.sheet.data trigger for automations. This is triggered when a sheet requests dynamic data. The automation returns a page of results and paging metadata. This will likely be merged into the new resources feature.

  • [Automations/Interactions] In ui.interaction automations, added a textarea: prompt to the await:form:elements: state. This displays a multi-line text input without the extra functionality of an editor.

  • [Automations] Renamed the yield: command to await:.

  • [Connected Accounts] On connected account records, added a uri field. This is a unique identifier (letters, numbers, dashes) used by other functionality (like automations) to refer to a connected account without using internal IDs.

  • [Records/Attachments] In attachment record dictionaries, added a download_url placeholder for the download link.

  • [Automations/Functions] In automations, merged ui.function triggers into ui.interaction. Interactions now have a headless mode to run without user interactivity. Interactions return their exit code and __return dictionary, so callers can determine errors. Moved function automations to automation.function, where they are general purpose and not related to the UI or toolbars.

  • [Automations] In the automation editor, the trigger is now selected using an interaction rather than a select menu.

  • [Automations] In automation interactions using .cerbBotTrigger(), added a start option for dynamically including interaction parameters. For instance, the currently selected text in an editor.

  • [Automations] In automation interactions, redefined the form: schema with children title: and elements:. This allows more configurability on forms. The title is used in the popup to differentiate interactions, and may change per step of the interaction.

  • [Data Queries/Extensions] In data queries, added a new platform.extensions type. This returns manifest data about plugin extensions for a given point: (e.g. cerb.card.widget). This makes it easier to incorporate extensions into interactions.

  • [Event Handler] In the event handler service, behavior: nodes now specify a uri: rather than an id:. The URI can be a behavior alias or ID. This change improves usability by making it possible to hyperlink URIs to open a card popup.

  • [Automations] In ui.interaction automations, added a fileUpload: element to forms. This prompts for a file upload and stores the new attachment ID.

  • [Platform/Plugins] Retired message toolbar extensions. These can be better implemented with interaction automations now.

  • [Toolbars/Automations] In toolbar KATA, menu nodes can specify a default: interaction using a key from the interactions. This will display as a split button, with the left-side running the default interaction instantly (e.g. “Reply”) and the right-side displaying the menu of alternative options (e.g. “Reply without quoting”).

  • [KATA/Automations] Fixed an issue in KATA parsing where blank lines following an empty array key: weren’t ignored. This affected the nesting depth.

  • [UI/Toolbars] On toolbars, menu buttons now expand on hover intent. This improves usability in editors because selected text remains focused when a click isn’t required to open a menu.

  • [Widgets/Interactions] On ‘interaction toolbar’ card/profile/workspace widgets, the default event/done is to reload the current widget.

  • [Interactions/Automations] Added a tooltip: option to toolbar KATA syntax.

  • [Interactions/Automations] ui.interaction automations now provide caller_name and caller_params inputs. This allows interactions to adapt for specific caller interfaces.

  • [Profiles/Tickets/Toolbars] Implemented custom toolbars for messages on ticket profiles. These are defined using toolbar KATA by editing the ‘Conversation’ widget. Any ui.interaction automation can be called with custom inputs:. This can also override the default actions for reply and comment by using those names for the interactions. Interactions receive the message ID and selected text as caller parameters. If the automation return dictionary contains a draft_id key, that draft will be opened with the reply editor at conclusion. If the return dictionary contains a refresh_widgets list, those widgets will be refreshed at conclusion (a shortcut value of all will refresh all widgets on the profile).

  • [Mail/Reply/Toolbars] Implemented custom toolbars for the email reply popup. These are defined using toolbar KATA. Any ui.interaction automation can be called with custom inputs:. The formatting options (e.g. bold, link, image) have bee replaced with interaction automations. Interactions receive the selected text as caller parameters. If the automation return dictionary contains a snippet: key, this text will be inserted into the editor at the cursor. The former mail.reply interaction point has been retired.

  • [Automations] In automations, record type inputs: now apply their default: value before processing the required: option.

  • [Platform] In DevblocksPlatform ::strAlphaNum() and ::strNum() the $also property is now properly escaped for regular expressions. This permits / as an allowed character and pattern delimiter.

  • [Resources] Implemented a new ‘resource’ record type for reusable assets that are primarily consumed directly by the client browser. This improves security by not repurposing attachments as generic storage for other functionality. For instance, map widgets can request relatively large GeoJSON resources (e.g. USA map by counties) and cache them in the browser. Community portals can request logos and custom stylesheets. Resources can be static (an uploaded file) or dynamic (generated on-the-fly by an automation using the resource.get trigger). Dynamic resources may provide a cache expiration, after which a client browser will request a new copy of the resource. Resources have a unique name (i.e. URI) that is referred to by other functionality instead of its internal ID. A client browser’s cached copy of a resource is automatically invalidated every time the resource changes on the server.

  • [Maps/Widgets/Automations] Map widgets can now display any GeoJSON data using resources. Previously, the only options were the built-in ‘World’ and ‘USA’ maps. Maps are now defined using KATA, which can include the underlying map, additional properties to fetch for regions, data sets of points, region color fills, click automations, and more. Maps can be displayed using various ‘projections’: mercator, albersUsa, and naturalEarth. The map can be centered on a specific latitude/longitude point by default, and zoomed to a specific scale. An arbitrary feature property can be used for labels (tooltips) when regions and points are selected (which defaults to name). This drastically improves reusability. For instance, an existing ‘World Countries’ map can be filtered to continent: Europe and then centered and zoomed on it. Similarly, a single ‘USA Counties’ map can individually display any individual county, or set of counties by state, or by multiple states. We now include several of these resources by default.

  • [Maps/Widgets/Automations] Map widgets can fetch a regions:properties:resource:uri: to merge new properties into existing map regions. For instance, a baseline ‘USA Counties’ map can fetch election results per county and merge those into properties, which are then used to colorize the map.

  • [Maps/Widgets/Automations] Map widgets can fetch a points:resource:uri: to overlay arbitrary points on an existing map. For instance, a baseline ‘World Countries’ map can fetch a data set for ‘World Capital Cities’ and display those points. This provides much more reusability than requiring a single GeoJSON resource with combied regions, points, and properties.

  • [Maps/Widgets/UX] Implemented pan and zoom functionality on map widgets. Previously, maps could only be zoomed by clicking on a region or point, but not moved. Now, maps can be zoomed using a mouse wheel or the ‘+’ and ‘-‘ buttons, and dragged by clicking on any region or point. Maps can’t be dragged by white space (e.g. ocean), which makes it easier to scroll past a widget without unintentionally zooming.

  • [Maps/Widgets/Automations] On map widgets, ‘Map clicked’ (map.clicked) automations can be defined in event handler KATA. The first enabled automation receives the event with inputs for the feature_type (region or point), feature_properties (from the GeoJSON), an optional widget_* dictionary, and the current worker_*. The return dictionary may include a sheet: key with a schema to display for the clicked feature, using the feature properties.

  • [Maps/Widgets] On map widgets, regions may now be color filled using dynamic data. fill:mode:choropleth targets a feature property (e.g. population) to shade regions proportional to their intensity (for instance, from subtotal data queries). fill:mode:color targets a feature property that defines an explicit color (e.g. red, blue). fill:mode:color_map targets a feature property and provides a fill:params:colors@list: that maps values to colors (e.g. California: blue).

  • [Data Queries] On worklist.geo.points data queries, format:geojson and format:topojson are now distinct.

  • [Maps/Widgets/Resources] Cerb includes three built-in resources for map widgets. map/world/countries is a map of world countries (properties: name, name_long, type, abbrev, postal, mapcolor13, pop_est, gdp_md_est, lastcensus, gdp_year, economy, income_grp, iso_a2, iso_a3, iso_n3, continent, region_un, subregion, region_wb), map/country/usa/states is a map of U.S. states (properties:name, geo_id, censusarea, state_id), and map/country/usa/counties is a map of U.S. counties (properties:name, geo_id, county, state, censusarea). These base maps can be filtered to display countless variations (e.g. continents, regions) without requiring any additional resources. Custom resources can be created for any particular need (e.g. German states, Indian states, Japanese prefectures).

  • [Automations] Implemented DAO_Automation::importFromJson() to automatically sync the built-in automations during installation and upgrades. This works like the package library and replaces the cerb.json file.

  • [Platform/Storage] When the storage service creates a storage_<namespace> table, it now adds an index for id_and_chunk rather than id and chunk individually. This fixes an issue with some MySQL installations (e.g. 8.0) that report the sort buffer is too small.

  • [Profiles/Ticket/Interactions] On ticket profiles, the ‘Participants’ widget now provides shortcut interactions for ‘add’ and ‘remove’.

  • [Maps/Widgets/Packages] In the package library, updated the workspace map widget examples (World/USA) to demonstrate the new functionality (zoom, pan, filter, color fill, click events).

  • [Maps/Widgets] Fixed an issue on map widgets when an invalid map:resource:uri was provided. The spinner kept running. The Promise now properly handles the rejection and displays an error.

  • [Maps/Widgets] On map widgets, implemented map:points:filter: for filtering points based on their properties. For instance, a resource can be loaded for world capital cities, but then filtered to continent: Europe. This works the same as filtering regions in map data.

  • [Maps/Widgets] On map widgets, when a region or point is clicked, the full list of properties is now displayed in a table. Previously only a single property like name was shown. This removes the need to use an interaction/sheet for many use cases, which is much faster by keeping the functionality in the client browser. A specific list of ordered properties can be displayed in the map:regions:label:properties: and map:points:label:properties: options. The value should be a list of property names as the key, with optional label: and format: number for each property.

  • [Resources] Improved the performance of retrieving large static resources. When over 1MB, a stream is used rather than loading the entire file into memory.

  • [Platform/URIs] Changed the Cerb URI format from uri:record_type:identifier to cerb:record_type:identifier.

  • [Resources] Removed slashes as a permitted character from resource URIs; added dots and dashes. This allows us to send the remaining path to a resource automation for dynamic behavior.

  • [Resources/Types] Implemented resource type extensions (e.g. “Map GeoJSON”). This makes it easier to filter for specific resource types in choosers and interactions, and improves security by making sure only expected resource types are available. It also allows for contextual validation and content previews.

  • [Maps/KATA] Refactored maps KATA syntax for filter: to be simpler and consistent.

  • [Maps/KATA] Refactored maps KATA syntax for fill: to be simpler and consistent.

  • [Maps/KATA] Implemented autocompletion for map KATA in the code editor.

  • [Maps] Abstracted map functionality so any feature can render a map using KATA. This is currently used by profile/workspace widgets and interactions.

  • [Maps] Implemented procedural pan/zoom functionality in maps. This is specified in map:projection:zoom: in KATA, with keys for latitude:, longitude:, and scale:. Omitting scale pans to the lat/long at the same zoom level. Omitting lat/long zooms to the given scale at the current point. For instance, an interaction displaying a point can zoom in on it. Unlike map:projection:center:, the zoom: option doesn’t change the map default center point or scale.

  • [Interactions/Maps] In interactions, map: prompts are now specified using map KATA. This makes it very easy to render dynamic maps.

  • [Maps/Resources] In map abstraction, added a catch-all promise rejection handler. This prevents the endless spinner when a resource doesn’t load as expected. In the future this should probably be implemented per resource to not block everything if one resource fails.

  • [Resources] Abstracted retrieving static/dynamic content on resources. Moved the logic out of the /ui/resource/ endpoints to make usage consistent and reusable. The content is always stored in a resource handle; when smaller than 1MB it’s stored in memory, otherwise in a temp file stream. Reimplemented caching on static resources. Resources that fail no longer instruct the browser to cache.

  • [Resources] Implemented the cerb.resource.map.points (Map Points) resource type.

  • [Resources] Implemented the cerb.resource.map.properties (Map Properties) resource type. The JSON response no longer requires nested properties keys.

  • [Maps] On map widgets, implemented map:points:size: for dynamically sizing points based on their properties. For instance, capital cities can appear larger than others.

  • [Maps] On map widgets, implemented map:points:fill: for dynamically coloring points based on their properties. For instance, capital cities can appear a different color than others.

  • [Maps/Resources] Included a built-in mapPoints.worldCapitalCities resource for the capital cities of the world.

  • [Maps/Resources] Included a built-in mapPoints.usaStateCapitals resource for the state capital cities of the United States.

  • [Maps] Improved map KATA autocompletion for map:regions:fill:.

  • [Maps] In map widgets, map:regions:label:title: and map:points:label:title: options control the title of the label for selected regions or points. This defaults to name.

  • [Maps/Resources] Built a new map.world.countries resource from Natural Earth data. This includes more detail, metadata, as well as the continent of Antartica, at roughly the same filesize as the earlier resource.

  • [Maps/Resources] Built a new map.country.usa.states resource from Natural Earth data. This includes more detail and metadata. This is a TopoJSON version of the cerb/geojson-maps file.

  • [Maps/Packages] Updated packages to demo new map features.

  • [Automations] In automation event handlers, changed the syntax for event/done: to after:.

  • [Automations/Policies] Cleaned up and enhanced automation policies. There’s a commands: parent to allow different policy rules (e.g. callers, actors). It’s now possible to specify all: to set a default for all commands. Removed the extraneous rule: nodes. Added explicit deny: rules. A failed allow does not deny, and a failed deny does not allow. Rules continue evaluating until they get an explicit allow/deny. The @bool annotation is implied if no annotations. The default is deny: yes if no rules match (or exist).

  • [Automations/Search] In automation worklists, search queries autocomplete the name: and trigger: fields.

  • [Interactions/Policies] Added a caller: policy to interaction automations. This allows an automation to determine who and where an interaction is available. If denied, an interaction is automatically changed hidden: yes in toolbars. If a worker invokes a denied interaction outside of a toolbar, an error message is displayed and it exits.

  • [Automations/UX] Disabled line wrapping in KATA code editors.

  • [Interactions/Await] In interactions that exit in the await state for continuations, a submit: element is now added to form:elements: only when one doesn’t exist. Previously, the submit was replaced in the form, or it could have two. This change allows an error message to display a submit with only ‘reset’ and not ‘continue’.

  • [Interactions/Automations] Renamed the cerb.trigger.ui.interaction trigger to cerb.trigger.interaction.web.worker. This provides a better organizational structure for other upcoming channel/audience interactions (e.g. sms.worker, slack.worker, portal.identity, web.visitor).

  • [Automations] Added an is_unlisted field to automations. This can be used to filter out automations that shouldn’t show up in choosers.

  • [Automation/Simulator] In automations, within a command’s on_simulate: event, added simulate.success: and simulate.error: commands. These respectively trigger the on_success: and on_error: events. An optional child dictionary is automatically set in the parent’s output: placeholder. This allows the simulator outcome to be conditional or randomized. An input key could specify a test case, which all on_simulate: events could base their output on.

  • [Automations] In automations, the error: command may now return a dictionary for the __return key. The error is returned in __error with at: and message: properties.

  • [Maps] Fixed an issue on map rendering where the empty legend was always visible.

  • [Maps/UX] Added a close button to the region/point label on maps. This makes it easier to close on small screens where the map is mostly occluded by the label.

  • [Automations] Updated automation syntax helpers to match the documentation.

  • [Automations] Automation names may now contain dashes and underscores.

  • [Platform/Automations] In automations and KATA, the @bool annotation is now generalized in the strings service as ->toBool().

  • [Automations] In automations, for the http.request: command, implemented the authentication: option using connected account URIs.

  • [Automations] In automations, on the storage.get: command, added a default: option that returns a default value for key misses rather than an error.

  • [Connected Accounts/URIs] Connected account URIs may now contains dots. Added a DAO::getByUri() method. Cards can now open with a URI or ID.

  • [Automations] In automations, added a while: command for conditional loops. The loop runs the do: actions while the if: parameter is true. This can implement controlled infinite loops for interactions and timers. Previously the only choice was repeat:, which expects a discrete list of values for each:.

  • [Connected Services/API] Added an ‘API Key’ connected service. This securely stores an API key, and authorizes HTTP requests by including the key in a named HTTP header or query argument. A ‘base URL’ option ensures the key is only included on requests to an expected HTTP endpoint.

  • [Maps] Fixed an issue with map KATA due to type coercion on null and integer 0 in the filter: options.

  • [Maps/Regions] In map KATA, region properties can be provided in regions:properties:data:. These are merged with regions:properties:resource: if both are provided.

  • [Platform/Plugins] Retired the ‘Sensors’ plugin. An improved version of this functionality is now possible through custom records and automations. The legacy plugin is available at: https://github.com/cerb-plugins/cerberusweb.datacenter.sensors/

  • [Setup/Scheduler/UX] Improved the style of Setup » Configure » Scheduler to make job titles stand out more.

  • [Platform/SDK] Updated the devblocks-dao.php helper script.

  • [Automations] In automation worklists, the ‘Type’ column now shows extension names rather than IDs.

  • [Automation/Timers] Implemented automation timers. A timer specifies a name, a future datetime, and a block of events KATA to conditionally determine an automation (of type automation.timer) to run at that time with optional inputs:. On the first invocation of the timer, an automation is selected. If the automation ends in the exit/error/return states, the timer is removed. If the automation ends in the await state with a new datetime, a continuation is created, and the timer is rescheduled for that future time. The timer stores the continuation ID and the automation pauses at the current point. On the next timer invocation, the automation resumes from the contination rather than starting over. A new scheduler job is responsible for running automation timers.

  • [Automations] Renamed automation ‘executions’ to ‘continuations’.

  • [Plugins] Retired the ‘Ticket Profile Move To Shortcut’ plugin. This can be reimplemented using interactions.

  • [Plugins] Retired ‘Profile Script’ extensions in plugins. These were used to modify profile pages, which can be done with toolbars now.

  • [Automations] Refactored automations to pass a model through to nodes and actions. The model caches the policy, environment, and abstract syntax tree. This allows nodes and actions to use data from the automation. For instance, the log: action can use the automation’s name in log entries.

  • [Automations] Added an encrypt.pgp: command to automations KATA. This encrypts a message using one or more PGP public keys.

  • [Automations] Added a decrypt.pgp: command to automations KATA. This decrypts a PGP-encrypted message using a private key.

  • [Automations/Security] In automations, using the say: command, Markdown output now sanitizes HTML in user input.

  • [Automations/Log] Implemented automation log records. The log: command in automations can write data to the log. Automations that exit in the error state also create entries to assist with error reporting and debugging.

  • [Automations/Log] In automations KATA, added commands for log: (debug), log.alert:, log.error:, and log.warn:. These log an arbitrary message of the given severity along with the current timestamp, automation name, and key path.

  • [Cards/Interactions/Toolbars] In ‘Record Fields’ card widgets, an interaction toolbar may now be specified. These items are displayed below the fields and appended to any (deprecated) search buttons. This allows fields and actions to be included in a single widget, obviating the need for an extra ‘Interaction Toolbar’ widget in many cases.

  • [Platform/Markdown] In the DevblocksPlatform::parseMarkdown() helper, an optional $safeMode parameter is now available. When enabled, this disables and escapes HTML markup.

  • [Records/Cards/PGP] When opening cards, PGP Public Key records may now be retrieved by ID or fingerprint.

  • [Platform/Plugins] Retired the ‘Legacy Printing’ plugin.

  • [Platform/Plugins] Retired the ‘Legacy Profile Attachments ZIP Download’ plugin. This will be replaced with interaction automations.

  • [Toolbars] Implemented toolbar records. This stores toolbar KATA for functionality that doesn’t have its own record (e.g. compose/reply toolbar, global interactions, card/profile toolbars).

  • [Mail/Compose/Toolbars] Implemented custom toolbars on the mail compose popup (cerb.toolbar.mail.compose).

  • [Behaviors] Retired the ‘Record editor opened by worker’ (event.ui.card.editor.opened.worker) behavior event. This can be reimplemented using card/profile interaction automations.

  • [Behaviors] Retired the ‘Before composing a message reply’ (event.mail.reply.pre.ui.worker) behavior event. This can be reimplemented using interaction toolbars.

  • [Behaviors] Retired the ‘During a message reply’ (event.mail.reply.during.ui.worker) behavior event. This can be reimplemented using interaction toolbars.

  • [Behaviors] Retired the ‘Before composing a new message’ (event.mail.compose.pre.ui.worker) behavior event. This can be reimplemented using interaction toolbars.

  • [Interactions/Behaviors] Retired bot interaction menus on record cards and profiles. These should be replaced with interaction automations.

  • [Mail/Reply] Implemented custom toolbars on the email reply editor (cerb.toolbar.mail.reply).

  • [Packages] Retired the ‘Profile Widget: Ticket Reply Draft Generator’ package.

  • [Packages] Retired the ‘Bot Behavior Action: Start Conversation’ package.

  • [Packages] Retired the ‘Bot Action: Execute jQuery Script’ package.

  • [Packages] Retired the ‘Bot Form Interaction Template’ package.

  • [Packages] Retired the ‘Bot Interaction Template’ package.

  • [Packages] Retired the ‘Reminder Bot’ package.

  • [Mail/Toolbars] Implemented custom toolbars when reading email on ticket profiles (cerb.toolbar.mail.read).

  • [Mail/Toolbars] Implemented a custom toolbar for the global interactions menu (cerb.toolbar.global.menu). In the 10.0 release, legacy chat bots are still accessible from this menu. These behaviors should be migrated to interaction automations before the 10.1 update.

  • [Time Tracking] Disabled the legacy time tracking profile script that adds buttons to toolbars. This needs to migrate to interactions.

  • [Cards/Toolbars] Implemented custom toolbars on card popups (cerb.toolbar.record.<type>.card).

  • [Profiles/Toolbars] Implemented custom toolbars on profile pages (cerb.toolbar.record.<type>.profile).

  • [Interactions] Added the ‘manage ticket participants’ interaction.

  • [Platform/Tests] Removed Travis-CI integration. We’re moving to GitHub Actions.

  • [Tests] Removed the installer, Selenium, and Webdriver test suites from PHPUnit. The tests are moving into the main Composer file for integration with GitHub Actions.

  • [Tests] Upgraded to PHPUnit 9.x and added a Composer ‘test’ script for build tools.

  • [Platform/PHP] Updated the minimum required PHP version to 7.4.

  • [Tests] Added a GitHub Action for running the test suite.

  • [Composer] Updated Composer dependencies.

  • [Platform/Devblocks/Classloader] Modified the Devblocks classloader API to separate ::registerPsr4Path() (namespaces) and ::registerClassPath() (non-namespaces).

  • [Platform/Dependencies] Removed Horde-IMAP from composer.json since PEAR repositories are no longer supported in Composer 2.0. We’ll load the project from a fork in a custom repository, and we’ll switch back when Horde officially supports Composer.

  • [Tests] Updated the platform requirement tests to allow an unlimited memory_limit.

  • [Tests] Updated the tests bootloader with classloading paths for automations. This allows tests to run without a completed installation or database.

  • [Tests] Updated the GitHub Action for build tests.

  • [PHP/Composer] Overloaded the singpolyma/openpgp-php dependency to a fork to support PHP 8.0.

  • [Platform/Dependencies] Updated Swiftmailer dependency from 5.4 to 6.2.4.

  • [Platform/Dependencies] Updated phpuseragent dependency from 0.14.0 to 1.1.0.

  • [Automation/Scripting] Added a kata_parse() function to automation scripting. This parses a KATA text block into a tree array.

  • [Automation/Scripting] Added a |kata_encode filter to automation scripting. This emits an object/array as a KATA text block.

  • [Platform/Dependencies] Updated oauth2-server dependency from 7.2 to 8.2.4.

  • [Records/Contexts] In record dictionaries, added a _type key with the URI. Also added a URI constant to the Context_ classes. This simplifies logic in scripting (e.g. automation policies).

  • [Automations/Events] Added a record type for ‘Automation events’. This allows event handler KATA to be configured for global events that don’t otherwise have a parent record (e.g. mail filtering).

  • [Automations/Events] In the events handler service, added a handleUntilReturn() method. This runs all events until one exits in the return state, and only that handler’s results are returned. This is used by events like mail.route, which require a single handler, but previous handlers exiting without results aren’t considered to be failures. For instance, a ‘Support routing’ automation can run first and decide it doesn’t handle the current inbound message; which allows a subsequent automation to check.

  • [Automations] Automations with custom inputs: can now provide a snippet: key for each input. This is used when inserting the automation into an event handler editor from an interaction.

  • [Automations] Tightened up permissions on automation records in the records API.

  • [Toolbars] Tightened up permissions on toolbar records.

  • [Composer] The horde-imap dependency is now loaded from a custom repository.

  • [Platform/Dependencies] Removed our patched version of Twig from the repository. This is moving to a custom repository in Composer.

  • [Platform/Composer] The ‘Twig’ dependency is now installed from a custom repository in Composer. This branch contains our patched version for registerUndefinedVariableCallback. Updated from version 3.0.2 to 3.1.1, which supports PHP 8.

  • [Bots/Behaviors] Retired ‘Respond to Ajax HTTP request’ (event.ajax.request) behaviors. This was triggered by widgets on the /ui/behavior endpoint. This should be reimplemented using interaction automations.

  • [Automations/Events] In the 10.0 update patch, built-in automation event records are inserted for mail.filter, mail.route, and record.changed. For filtering and record changed, events KATA is generated for existing behaviors.

  • [Automations] Implemented the cerb.trigger.record.changed event in automations. This event is triggered whenever one or more fields are changed on a record. In the events KATA, all enabled automations are executed in order. Automations can dynamically be enabled/disabled by record type, field values, or actor. Automations are not expected to return: any outputs. Any existing bot behaviors on the ‘Record changed’ or ‘New ticket created’ events are automatically added to the events KATA and no longer trigger independently.

  • [Bots/Behaviors] Tagged all bot behavior event names with ‘(Legacy)’. These should no longer be used whenever possible. Behaviors should be migrated to automations.

  • [Bots/Behaviors] The 10.0 update disables behaviors on previously retired events.

  • [Platform] In DevblocksDictionaryDelegate->getDictionaryFromModel($model) the passed model can be either an object or an array. Some record types accept either, and the record.changed automation event uses the associative array format.

  • [Platform/Extras] Updated the devblocks-dao.php code generator to use recent platform updates and conventions.

  • [Automations/Events] Bot behaviors on the ‘Record commented on’ (event.comment.created.worker) and ‘New comment on ticket in group’ (event.comment.ticket.group) events have been moved into the events KATA in the ‘cerb.trigger.record.changed’ automation event. These no longer trigger on their own and should be migrated to automations.

  • [Automations] In automations, the encrypt.pgp: command now accepts an public_keys:ids: option. This makes it simpler to directly pass in a list of public keys from a sheet chooser placeholder.

  • [Automations/Triggers] Updated all automation triggers to properly document their inputs:.

  • [Automations/Scripting] Improved error reporting on automation scripting. When parsing Events KATA, the key path and relative line number are returned for errors. Previously there was no error reporting when syntax was incorrect in KATA.

  • [Automations/Events] Refactored automation events. Their names no longer include the cerb.trigger. namespace prefix. Instead, an extension_id field references the Extension_AutomationTrigger. It’s no longer possible to create or delete automation event records in the UI (even during development). These must be added by a schema patch.

  • [Automations/Events] All ‘Events KATA’ editors now have toolbar options for ‘Placeholders’ and ‘Test’. These are implemented abstractly. The placeholders button opens a reference for the appropriate automation trigger. This makes it easier to script. The test button allows simulated placeholder values and displays which event handlers would respond to the event (e.g. based on conditional disabled@bool: logic).

  • [Toolbars] Refactored toolbars. Toolbars can no longer be created or deleted through the UI (even by admins or during development). They must be managed in a schema patch.

  • [Toolbars] Added error reporting when parsing toolbar KATA.

  • [Toolbars] Toolbar names no longer have cerb.toolbar. prefixes. Instead, toolbar records have an extension_id field that links to an extension. This also allows per-toolbar helpers in the UI.

  • [Toolbars] Card and profile toolbars now share the record.card and record.profile toolbars. This makes it simple to add an interaction to every record, or any subset of records.

  • [Toolbars] All ‘Toolbar KATA’ editors now have toolbar options for ‘Placeholders’ and ‘Test’. These are implemented abstractly. The placeholders button opens a reference for the appropriate toolbar. This makes it easier to script. The test button allows simulated placeholder values and displays which items would be displayed in the toolbar (e.g. based on conditional disabled@bool: logic). This also does syntax checks on the KATA.

  • [Automations/Scripting] In automation scripting, added a new is [not] pattern test. This makes it much simpler to implement conditionals like disabled@bool: and hidden@bool:. The test accepts one or more patterns where asterisks (*) denote wildcards. For instance: {{placeholder is pattern ("support@*", "*@example.com")}}. The placeholder can be a string or an array. The test automatically returns a 1 when true, so no output is required.

  • [Automations/Scripting] In automation scripting, added a new is [not] prefixed test. The test accepts one or more prefixes to test the placeholder against. For instance: {{placeholder is prefixed ("**[Bugs]**")}}.

  • [Automations/Scripting] In automation scripting, added a new is [not] suffixed test. The test accepts one or more suffixes to test the placeholder against. For instance: {{placeholder is suffixed (".com")}}.

  • [Automations] The record.changed automation now has an is_new placeholder. This is true if the record was created during the current request, making it much simpler to react only to new records rather than updates.

  • [Automations/Mail/Filtering] Implemented the mail.filter automation event. This can modify or reject an inbound message based on its properties (e.g. sender, subject, recipients, headers, body) before it is accepted.

  • [Automations/Mail/Routing] Implemented the mail.route automation event. This determines a group inbox given properties of an incoming message (e.g. sender, subject, recipients, headers, body).

  • [Platform/Strings] Added DevblocksPlatform::services()->string()->capitalizedDashed() to capitalize the segments of a dashed-string. This is used to format MIME headers, etc.

  • [Automations/Events] In automation events KATA, there can now be multiple enabled: or disabled: rules. The first rule to return true is used. This allows deny-allow and allow-deny strategies. By default handlers are all enabled.

  • [Profiles/Interactions/Toolbars] In ‘Record Fields’ profile widgets, an interaction toolbar may now be specified. These items are displayed below the fields and appended to any (deprecated) search buttons. This allows fields and actions to be included in a single widget, obviating the need for an extra ‘Interaction Toolbar’ widget in many cases.

  • [Workspaces/Interactions/Toolbars] In ‘Record Fields’ workspace widgets, an interaction toolbar may now be specified. These items are displayed below the fields and appended to any (deprecated) search buttons. This allows fields and actions to be included in a single widget, obviating the need for an extra ‘Interaction Toolbar’ widget in many cases.

  • [Validation] Fixed an issue with email field validation where extraneous data like personal names were allowed. This validation now returns only the mailbox@host part.

  • [Records/Dictionaries/Performance] Improved the performance of address dictionaries. These used a contact name to generate their label, which could lead to extra lookups if contacts weren’t bulk loaded. The _label is now lazy loaded.

  • [Worklists/Performance] On ticket worklists, improved the performance of the ‘Participants’ column. This was doing N+1 lookups for contact names. This data is now properly loaded once in a batch.

  • [Automations/Continuations] In automation continuations, added ‘parent token’ and ‘root token’ fields to the schema. This simplifies and optimizes the implementation of ‘delegate’ interaction chains.

  • [Sheets] Sheet schemas may now specify parameters for layout styles.

  • [Platform/OpenID] Refactored our OpenID Connect implementation to be compatible with lcobucci/jwt 4.x changes.

  • [Sheets/Layouts] When using sheets in the worker UI, added a ‘buttons’ layout that displays rows as buttons. This also supports one-click continue.

  • [LDAP/Security] The LDAP connected service now requires StartTLS encryption on port 389. On port 636, LDAPS is now automatically configured.

  • [Automations/KATA] In KATA, added a @date key annotation. This converts a human-readable absolute (Jan 1 2025 08:00) or relative (+2 hours) date text value into a Unix timestamp. Previously this was done with less intuitive automation scripting ({{"now"|date('U')}}).

  • [Automations] In automations, the record.upsert command record_query: input can now specify limit:1 to update a record even when there are multiple matches. The sort: filter of the query determines the record to use (whatever is first).

  • [Automations/Tests] In automation scripting, added a is [not] record type ("type") test. This compares an expression to one or more record types. Record types can be specified as extension IDs (e.g. cerberusweb.contexts.ticket) or URIs (e.g. ticket). Previously the much less intuitive |context_alias filter was required to ensure the expression and comparison were both in the same format.

  • [Automations] In automations, outcome: commands can now occur on their own without a decision: parent. These are simple conditionals rather than switch cases.

  • [Automations] In automations, added a record.search: command to return record dictionaries from a search query. Previously this required the more complex data.query: command with worklist.records type. This has inputs for record_type:, record_query:, and record_query_params:. The latter is for parameterized queries with untrusted user input. If limit:1 is used in the query, the result will be a single record dictionary rather than an array of them.

  • [Automations/Interactions/Sheets] In interaction automations, sheet: form elements now synthesize data: when only given a list of keys without properties.

  • [Custom Records] When procedurally creating custom records (e.g. automations, bots, API), if the only possible owner is ‘(app) Cerb’ then this is automatically applied when blank. Previously, the owner__context and owner_id fields were required, which caused confusion.

  • [Records/Drafts] On draft records, the worker_id field is now only required for these types: mail.compose, ticket.reply, and ticket.forward. In other cases (e.g. transactional), there may be no worker because the author is an automation or bot.

  • [Worklists/Import/UX] On worklists when importing records from CSV files, fixed an issue where multiple ‘final results’ notifications were generated if the ‘Preview’ option was used.

  • [Mail] Refactored the mail service to interchangeably accept markdown or parsedown as the same content_format option. For whatever reason we’ve been using parsedown as the official value rather than the far more intuitive markdown.

  • [Security/Worklists/Search] In worklist search functionality, the getParamsFromQuickSearch and getFieldsFromQuery methods now accept an optional array $bindings argument. This is used to safely substitute ${placeholders} from untrusted user input. Normal `` are replaced before the query is tokenized/parsed. The bindings are replaced after the query is parsed, so they can’t add new tokens and only affect a single T_TEXT or T_QUOTED_TEXT token.

  • [Composer/Security] Updated Smarty to 3.1.39 from a security advisory.

  • [Data Queries/Security] Implemented support for parameterized data queries when dealing with untrusted user input.

  • [Automations/Data Queries] In automations, the data.query: command now supports query_params: for untrusted user input.

  • [Automations/Records] In automations, the record.upsert: command now supports record_query_params: for untrusted user input.

  • [Mail/Drafts] In draft records, added a new mail.transactional type. This queues outgoing transactional messages for delivery without requiring a worker (e.g. automations, bots) nor opening a ticket (like compose). Previously, an action like ‘Send email’ immediately delivered a message, which could significantly affect the performance of automations; especially when delivering hundreds or thousands of messages (as is the case with surveys or mailing lists). As well, such messages weren’t retried when encountering temporary delivery issues. This draft type will deliver in a background queue and retry failures.

  • [Interactions/Website] Implemented the interaction.website trigger for automations. This provides interactions with visitors on third-party websites. Interactions may enter the await state any number of times for multi-step workflows where user input is required (e.g. a form). At conclusion, the return dictionary is returned to the caller. On websites, this can be used for surveys, sign-up forms, contact forms, troubleshooters, customer service bots, and much more. This deprecates the older ‘Chat Bots for Websites’ plugin.

  • [Interactions/Website] Website interactions can be added to any website using this single tag above the </body>: <script id="cerb-interactions" src="https://cerb.example/portal/surveys/assets/cerb.js" type="text/javascript" defer></script>. A data-cerb-interaction attribute can then be added to any DOM element on the website (e.g. links, buttons, images) to start the interaction on click. The value can be any arbitrary interaction name. Additionally, a data-interaction-params attribute on the same element can pass arbitrary URL-encoded parameters (e.g &param1=value1).

  • [Interactions/Website] Website interactions can be automatically started on any website page by appending an anchor to the URL in the format: #/interactionName&param1=value1, where interactionName is any interaction name.

  • [Interactions/Website] Website interactions use events KATA to start a specific interaction.website automation based on the given parameters.

  • [Interactions/Website] Implemented a responsive UI for website interactions. This adapts to different devices: desktop, laptop, tablet, smartphone.

  • [Interactions/Website] Implemented the first batch of form elements for website interactions: end, say, sheet, submit, text, and textarea.

  • [Interactions/Website] Implemented delegate support for website interactions. When exiting in the await state, an interaction:uri: key can be provided with the URI of another website interaction to start. An optional interaction:output: key determines the placeholder to set with the results of the delegate on completion. When the delegate ends, the caller automatically resumes from the same place. Delegates can be nested to any depth. For instance, a reusable delegate could handle email or SMS validation, and be shared by many other interactions.

  • [Interactions/Website] In website interactions, the <script> is implemented with plain Javascript. The previous bot portal required the jQuery library.

  • [Interactions/Website/Security] Implemented Cross-Origin Resource Sharing (CORS) for website interaction portals. Multiple allowed origins can be specified. An entry of * allows any origin.

  • [Interactions/Automations] In worker interactions, added await:interaction: for temporarily handing control to another delegate interaction. When the delegate exits, the parent resumes. This makes interactions much more modular and reusable. An await:interaction:output: key specifies the placeholder that should receive the results.

  • [Toolbars/Interactions] Added a configurable toolbar to the automation editor (automation.editor).

  • [Records/Worklists/Drafts] In worklists of draft, primary links now always open the record card popup.

  • [Mail/Performance] Implemented a cache for message HTML content when using a remote storage engine (e.g. Cerb Cloud, S3, Gatekeeper). This avoids the round-trip on repeated displays of a ticket message in a short time period. Cached content currently expires in 4 hours and is flushed by the cron.heartbeat scheduler job.

  • [Automations] In automations, the http.request: command now accepts an inputs:timeout: option. This is a decimal number of seconds to enforce as the maximum connection/read time on the request. For instance, 0.5 for 500ms. [#153]

  • [Mail/Drafts] The name of email draft records now support utf8mb4 (emoji). [#1338]

  • [Automations/Interactions/Mail] Added a cerb.mail.compose interaction automation for generating compose drafts for address, contact, and organization records.

  • [Packages] Updated the ‘Compose’ packages for card widgets on address, contact, and org records. These now use the new interaction automation rather than form-based behaviors.

  • [Mail/Drafts] On draft records added a new token field. This is a random unique identifier for the draft which is copied to the eventual message record after a compose/reply. This allows customer-facing functionality (like survey links) to securely reference an eventual message before it’s created. Previously this was difficult to implement because a message record wasn’t created until after an email was successfully sent.

  • [Mail/Messages] On message records added a new token field. This is a random unique identifier for the message that can be used in customer-facing workflows (e.g. customer satisfaction survey links). When a message is created from a draft, the token of the draft and message will be the same. This makes it easy to reference a future message in links when sending a draft.

  • [Mail/Drafts/UX] Compose and reply drafts can now be resumed directly from card popups. There is a ‘Resume’ button that opens the email editor.

  • [Mail/Drafts] Implemented an abstract endpoint for resuming drafts. Previously there were separate endpoints for compose and reply. This change makes it much easier to resume drafts from interactions, draft card popups, etc. This only requires a draft ID or token, and not advance knowledge of the type of draft.

  • [Mail/Drafts] On the compose and reply editors, events are now properly triggered for all outcomes (send, save, discard).

  • [Interactions/Automations] In worker interactions, added a new await:draft: continuation. This takes a uri: parameter which specifies a compose or reply draft to resume by opening the email editor popup. An automation can create a draft record prior to this step. The interaction pauses until the draft editor is closed. An optional output: parameter specifies a placeholder to store the status of the draft after editing (e.g. sent, saved, discarded). This allows the interaction to make decisions based on those outcomes.

  • [Records/Validation] In record field validation, text-based fields now automatically truncate overly long inputs using the configured max length. Previously this always returned an error, which was often undesirable in background automations and behaviors. This automatic truncation can be disabled on a per-field basis when configuring record types.

  • [Automations/Logging] Automations now automatically log errors when exiting in the ‘error’ state. This makes it much easier to catch issues with monitoring.

  • [Automations/Logging] In the automation editor, a ‘Log’ tab now displays the log entries for the current automation.

  • [Time Tracking] The time tracking plugin no longer adds a timer button to profile pages. This can be replicated with interactions and toolbars.

  • [Interactions/Automations] In worker interactions, added a new await:record: continuation. This takes a uri: parameter which specifies a record type (and optionally record ID) to open in an editor popup. When an ID is omitted the editor is opened in ‘create’ mode. The interaction pauses until the record editor is closed. An optional output: parameter specifies a placeholder to store the status of the record after creation/modification (e.g. saved, deleted, aborted). This allows the interaction to make decisions based on those outcomes.

  • [Project Boards/Automations] Added a new cerb.projectBoard.record.create interaction automation for creating records of any type and linking them to a project board column. This is used in the project board examples and packages.

  • [Storage/Attachments] A storage object is now considered to be a duplicate when it has the same SHA-1 hash, size, and file type (e.g. image/png). Previously, storage objects were required to have the same file name to be considered a duplicate. In several cases, email clients rename existing files (e.g. after virus/malware scanning). Combined with the #original_message feature, this could result in hundreds of duplicate files for images in signatures, etc. [#1441]

  • [Records/Opps] Implemented autocompletion for opportunity records when used in ‘Record link’ custom fields.

  • [Automations/Inputs] In automation inputs: using the record type, the value can now be provided as a record ID or a URI (e.g. cerb:record_type:record_alias). This makes automations more portable.

  • [Platform/OAuth] Fixed an issue with OAuth1 with Twitter API changes where the signature_method isn’t always provided. This defaults to hmac-sha1.

  • [Packages/Toolbars] Packages can now append to existing toolbar records.

  • [Automations] On automation inputs: with the record/ or records/ types, an expand: option can now be provided with a list of keys to preemptively expand.

  • [Records/Tickets/Search] On ticket worklists, added comments.first: and comments.last: filters. These match only the first or last comment. This is helpful in building worklists of @mentions or tags using comments.

  • [Automations/Scripting] In automation scripting, added an array_fill_keys(keys,value) function. This creates an array with the given keys, each set to the default value. For example, {{array_fill_keys(range(1,10),true)}} returns {"1":true,"2":true,"3":true,"4":true,"5":true,"6":true,"7":true,"8":true,"9":true,"10":true}.

  • [Data Queries] worklist.subtotals data queries can now perform aggregate functions on a single by: field. Previously this required two or more fields.

  • [Data Queries/Performance] Implemented a timeout: option for all worklist.* data query types. This is a number of milliseconds to enforce as a time limit for the query. It defaults to 20000 ms (20 secs). Previously there was no limit on long-running data queries, which could cause database congestion.

  • [Calendars] In calendars, fixed an issue with clicking the day of the month to quickly add new events.

  • [Automations/Events/Help] Added autocompletion to Events KATA editors.

  • [Automations/Toolbars/Help] Added autocompletion to Toolbar KATA editors.

  • [Sheets/Help] Added autocompletion to sheet KATA editors.

  • [Dashboards/Help] Added autocompletion to dashboard KATA filters.

  • [Automations/Triggers] Implemented the ui.widget trigger for automations. This allows custom output to be implemented for card, profile, or workspace widgets. This replaces bot behavior-based widgets, which are now deprecated.

  • [Cards/Widgets/Automations] Implemented ‘Automation’ card widgets. This triggers a ui.widget automation when the widget is rendered.

  • [Profiles/Widgets/Automations] Implemented ‘Automation’ profile widgets. This triggers a ui.widget automation when the widget is rendered.

  • [Workspaces/Widgets/Automations] Implemented ‘Automation’ workspace widgets. This triggers a ui.widget automation when the widget is rendered.

  • [Interactions/Website] Website interactions can now ‘invoke’ async server-side actions. For instance, allowing a sheet: form element to page prev/next without submitting the form.

  • [Interactions/Worker/Sheets] In worker interactions, sheet: form elements with static data now support paging. Previously this only worked for dynamic data sources.

  • [Interactions/Website] In website interactions, implemented paging for sheet: form elements. This also preserves multiple selections across pages.

  • [Interactions/Automations] Renamed the automation trigger interaction.web.worker to interaction.worker.

  • [Platform/Links] Multiple target $context_ids can now be passed to DAO_ConectextLink::getContextLinkCounts(). Previously this only accepted one ID at a time. This is used to prefetch links for all messages/comments on a ticket profile, etc.

  • [UI/Links] Fixed a minor UI issue with ‘Links’ button bars. It was possible for buttons to wrap strangely, with several on the same line, then the final two on their own lines.

  • [Links/UX] On ‘Links’ button bars, the labels are now pluralized. For instance, “(5) Tickets” vs “(5) Ticket”. Previously these were all singular.

  • [Automations/Sheets] In automations, simplified ui.sheet.data paging. Previously this required a complex data structure. Now an automation can just return an array of data: and a total: of the number of rows before paging. The sheet_limit, sheet_page, and sheet_filter inputs are now more obvious.

  • [Interactions/Sheets] In interactions, sheet prompts can use data:automation: rather than data:function: to load data dynamically from an automation. The latter was an artifact from originally using automation.function triggers. Now we use ui.sheet.data. Syntactically, data:function: could be confused with the function: command.

  • [Interactions/Sheets] In worker and website interactions, implemented the sheet:layout:filtering: option for static data: sets.

  • [Records/Links] On record links components, implemented a “compact” mode. This can be used to show links inline (like on comments or messages) without the full fieldset using vertical space.

  • [Records/Links] On ‘Links’ components, fixed an issue with clicking on the circular count label of a button not opening the results.

  • [Automations/UX/Autocomplete] On KATA code editors (e.g. automations, sheets, maps) syntax highlighting and autocompletion is now available for scripting syntax (Twig).

  • [Automations/Timers] Recurring schedules are now much easier on automation timers. When editing an automation timer, a new ‘Repeat:’ option is available. This configures any number of cron-like patterns and a timezone. In the case of conflicting patterns, the earliest is selected as the next occurrence. With a repeating timer the ‘When:’ field is optional and defaults to the next occurrence of the patterns. On conclusion, a repeating timer will be rescheduled in the future, and a non-repeating timer will be disabled.

  • [Automations/Timers] Automation timers no longer delete themselves after running. A timer can exit in the await: status an until@date: key any number of times. This creates and resumes a continuation. Timers can be deleted by explicitly outputting return:delete@bool: yes.

  • [Automations/Timers] Automation timers can now be disabled. This preserves their configuration but ignores them in the scheduler until re-enabled.

  • [Automations/Timers] Automation timers now independently keep track of their ‘Last ran at’ and ‘Next run at’ timestamps.

  • [Automations/Timers] In automation timer editors, an interaction now provides examples for recurring schedule patterns.

  • [Records/Groups] In group record dictionaries, a new default_bucket_ key prefix is available for expansion.

  • [Automations/Triggers] Implemented the record.merge trigger for automations. This makes it possible to conditionally approve or deny a record merge request based on the records and current worker. This occurs after merge mapping. The input is the record_type, source_ids, target_id, and dictionaries for records and the current worker_*. This uses events KATA, and the first returning automation is used. For instance, a policy could require that tickets can only be merged if they share the same group or participants. If the automation returns a deny: key, the merge is denied, and the key’s value is the displayed message with the reason. Thanks to @1password for the request!

  • [Mail/Drafts] Fixed an issue on drafts. When unchecking ‘Deliver Later’ the future delivery date is now automatically cleared if set. Thanks to @tecwerks for the report!

  • [Automations/Tickets] Added an example cerb.ticket.move interaction automation. This demonstrates how to provide a customizable ‘Move to’ shortcut on ticket profiles and cards.

  • [Attachments/Cards] Attachment cards now show link counts on a file even if a worker lacks permission to view its contents. This makes it easier to find out who is authorized to view it.

  • [Attachments/Security] When a worker uploads a file, they are now granted direct access to it. Previously, if a worker uploaded a file that was a duplicate of an existing file, and they lacked access based on the existing privileges (e.g. private group), the worker couldn’t see that file’s content in compose/reply previews or card popups. If a worker uses their permissions to attach the file to a new message, other workers who have permission to view that message will have permission to view the file.

  • [Records/Workers] In worker record dictionaries, added an emails expansion key. This returns an array of dictionaries for a worker’s primary and alternate email addresses.

  • [Records/Workers] In worker record dictionaries, added a groups expansion key. This returns an array of dictionaries for a worker’s group memberships.

  • [Records/Workers] In worker record dictionaries, added a roles expansion key. This returns an array of dictionaries for a worker’s role memberships.

  • [Automations] In automations, added a var.expand: command. This takes a key: and paths: as input. The key specifies the location of one or more dictionaries. Each of the specified paths will be expanded in those dictionaries.

  • [Automations] In the automations editor, added autocompletion for all inputs: KATA.

  • [Connected Services] Fixed an issue in OAuth2 and OIDC connected services where the ‘Scope:’ parameter was artificially limited to 255 characters. The limit has been raised to 4096.

  • [Automation/Scripting] In automation scripting, added a |parse_url filter. This takes a URL as string and returns an object with keys for: scheme, host, path, query, and fragment. Thanks to @mryanb for the feature request!

  • [Search/Sphinx] The Sphinx search extension is deprecated and will be removed in v11.0.

  • [Performance/Search/Custom Fields] Improved performance when filtering records by link-based custom fields. In some specific conditions (custom record, with custom fields including a link field), queries could time out even with simple data sets, due to do with the MySQL ‘firstmatch’ semi-join strategy. This change gives better hints to the MySQL query planner to avoid the issue (in one case resulting in a >600X improvement).

  • [Performance/Tickets/Search] Improved the performance of the closed: filter on ticket records. This lacked a database index.

  • [Performance/Search] Long running full-text searches (e.g. message content) will now time out after 15 seconds. Previously these were not subject to time limits and could cause excessive demand on the database.

  • [Records/Events] Message records now trigger record.changed automation events.

  • [Interactions/Automations] The global interactions menu (i.e. floating Cerb icon in lower right) now allows keyboard navigation in the menu.

  • [Custom Records/Watchers] Fixed an issue with the watchers: filter on custom records. Thanks to @1password for the bug report!

  • [Mail] Fixed an issue with the #original_message command in replies. If a message contained a dollar sign followed by a number (e.g. $1), or a slash followed by a number (\1), those characters were being removed in the quoted content.

  • [Toolbars] On record card and profile toolbars, fixed an issue with the worker_ placeholders not being initialized on the first render. These only worked properly after refreshing the toolbar. Thanks to Corey at @1password for the bug report!

  • [Records/Search/Custom Fields] In record search queries, file-based custom fields now properly have search filters. Thanks to @mryanb for the bug report!

  • [Mail] Fixed an issue with the ‘Send again’ feature on ticket profiles. There was an API change in the Swiftmailer dependency.

  • [Automations/Logs] Improved the formatting of the ‘Log’ tab in the automation editor.

  • [Automations/Logs] Fixed an issue with automation logs. Log entries that were over 1024 characters failed to save. They are now truncated to the limit and saved properly.

  • [Automations] In the automation editor, fixed an issue with the ‘Actions->Function’ helper in the menu. This was inserting an older name: option rather than the current uri: syntax.

  • [Sheets] Fixed an issue with sheets where a long line with no word breaks (spaces) was stretching the table rather than hard wrapping. This was most apparent in automation logs with JSON output.

  • [Widgets/Form Interactions] Fixed an issue with card, profile, and workspace ‘Interaction Toolbar’ widgets where legacy behaviors YAML didn’t work when prefixed with ---.

  • [Update/Bots] In the 10.0 migration patch, all behaviors on disabled bots are now marked disabled. This ensures those behaviors are properly defaulted to disabled when referenced by other functionality (automations, toolbars). [#1450]