New calculation, undo and redo, agent framework, server-side and design system features.
What's New Copy Link
AG Studio 3 introduces:
- Calculations - Build and edit field expressions in the UI.
- Undo & Redo - Step backwards and forwards through dashboard changes.
- Studio Agent Framework - Build custom AI agents on the Studio agent harness.
- Server-Side Pagination - Page through server-side data a request at a time.
- Figma Design System - A Figma library matching the Studio interface.
These features introduce certain breaking changes, as listed below.
Breaking Changes Copy Link
The full list of breaking changes in AG Studio 3. These entries are written to be read by an AI agent as well as by you, so each one is deliberately verbose: it spells out what changed, what an application relying on the old behaviour will see, and how to mitigate it.
To automate the upgrade, use the ag-update skill, which reads this migration guide, checks it against your repository, and produces a plan of the changes needed.
AI Agent Framework Copy Link
The ai property on AgStudioProperties has been rebuilt around the Studio Agent Framework, and a version 2 configuration does not carry over. This is a re-architecture rather than a rename: expect to reimplement what you passed to ai, rather than to adjust it.
In version 2 the property was an AgAiAssistant: one object that was both the connection to your LLM, through executeTurn, and the agent roster, through agents and primaryAgent. Those are now separate concerns. The property takes a harness - the thread and agent layer the chat panel renders from - and the LLM connection is an adapter the harness's agents run against.
The property is now an AgAiHarnessSetup: an AgAiHarness, or a function building one from the Studio API. createAiHarness builds Studio's own, and pairs each built-in agent with your adapter:
// Version 2: one object, holding both the provider connection and the roster.
const studioProperties = {
ai: {
executeTurn: (request) => myAssistant.executeTurn(request),
agents: [...agStudioDefaultAgents, myAgent],
primaryAgent: 'lead',
},
};
// Version 3: a harness, given an adapter to run the agents against.
const studioProperties = {
ai: ({ api }) => createAiHarness(api, { adapter }),
};Four things changed shape underneath that, and each is documented in full in the AI section:
- The provider connection is an adapter.
AgAiAssistanthas becomeAgLlmAdapter, withAgAiRequest,AgAiResponseandAgAiResponseHandlerbecomingAgLlmRequest,AgLlmResponseandAgLlmResponseHandler.executeTurnstill runs one turn against your provider, but its stream now yields the AG-UI events ofAgAiEventin place of thestatus,item,partanddeltaevents ofAgAiStreamEvent, so a version 2 adapter's decoding half needs rewriting. - An agent is a definition plus a runner.
AgAiAgent, with itstypeand itsconfig: (params) => AgAiAgentConfig, has become a flatAgAiAgentDefinitionpaired with a runner that answers for it:directLlmRunnerwhen Studio should run the loop against your adapter,clientToolRunnerwhen you answer each turn, or your ownrunwhen the loop is yours. - Instructions and tools are resolved per run. They were read once from
config(params); they are now callbacks re-read on every run, which is what lets a tool's schema carry live field and widget ids. A version 2 agent that built its instructions eagerly must move that work inside the callback. - Tools and delegates are objects, not names.
toolstookAgToolRefnames anddelegateAgentstook agent ids. Both are now built: Studio's tools come fromapi.getAiTools()or the config builder'stools.studio, and delegation fromtools.delegateTo([ids]).agStudioDefaultAgentshas been replaced by the config builder'sbuiltIn, which hands you the five Built-In Agents as definitions to pair with a runner.
Conversations are no longer part of report state: see AgReportState.ai under Removed APIs. Module registration is unchanged - AgStudioAiModule is still what opts the feature in.
Cross Filtering Copy Link
A cross filter selection now belongs to a group, and group is a required property. A group is a zero-based number that identifies which of a widget's selections is being set. A widget can hold one selection per group, and selections in different groups are independent of each other, so a widget that selects on several fields at once gives each field its own group.
group has been added as a required property to:
AgCrossFilterSelectionValuesandAgCrossFilterSelectionRange, returned byAgWidgetApi.getCrossFilterSelections().AgCrossFilterSelectionValuesStateandAgCrossFilterSelectionRangeState, used in thecrossFilterpage state.AgWidgetCrossFilterValueParamsandAgWidgetCrossFilterRangeParams, passed toAgWidgetApi.toggleCrossFilter().
A custom widget that holds a single selection should pass group: 0:
api.toggleCrossFilter({ type: 'value', field, value, group: 0 });Selections are resolved against the group they are set on.
Saved state from a version prior to 3.0.0 is migrated automatically on load: each selection that has no group takes its position in the widget's selection array, so the first selection becomes group 0, the second group 1, and so on. Selections that already carry a group keep it. You only need to update code that reads or sets cross filter selections directly.
Removed APIs Copy Link
AgReportState.aihas been removed. AI assistant conversations are no longer part of report state: they are owned by the agent harness, and are persisted by passing ahistorystore (AgAiHistoryStore) in theAgAiHarnessConfiggiven tocreateAiHarness()- see Harness Overview.AgPageState.schemahas been removed. Use the report-levelAgReportState.schemainstead. Saved state from a version prior to3.0.0is migrated automatically on load: each page's schema fields are hoisted into the report-level schema.CYCLIC_FRAGMENT_IDS,isGeneratedCalendarandisAdoptedCalendarhave been removed from the package entry point with no replacement. They were internal helpers that were exported by mistake.framehas been removed fromAgWidgetConfigandAgWidgetDefinition, along with theAgWidgetFrameStyletype. Widget framing is now part of the formatting config and can be customised per widget instance through the border properties underformat.widget(borderEnabled,borderWidth,borderColor,borderRadius). Remove anyframeproperty from your widget configurations and definitions.nullsFirsthas been removed fromAgCubeAxisLevelSort. UsenullPlacementinstead, which names all four placements rather than the two a boolean can express. ReplacenullsFirst: truewithnullPlacement: 'nullsFirst'andnullsFirst: falsewithnullPlacement: 'nullsLast'. An axis level that set neither now takes the engine-wideAgNullHandlingOptions.nullPlacement, which defaults to'nullsMin': a null sorts as the smallest value, so it comes first ascending and last descending. The previous default was not one rule but two, and which one you were getting depended on what the level sorted by: an axis level sorted by a dimension value behaved as'nullsMax', and one sorted by a measure behaved as'nullsLast'. To keep the previous ordering, set whichever of the two matches the level.AgStudioModulenow contains onlymoduleNameandversion, andmoduleNameis typed byAgStudioModuleName, which has been narrowed to the module names you can register. The other properties, and the types that described them, were internal implementation details:SingletonBean,AgLicense,ExternalModuleNameandInternalModuleNamehave all been removed from the package entry point with no replacement. Modules are still registered in the same way.- A calculated column or measure no longer carries
expression,columnsandfieldsetDependenciesdirectly. Its compiled form has moved to acalculationproperty (AgFieldCalculation), holding the compiled expression and the columns and fieldsets it depends on, andexpressionnow holds either the authored expression string or its parsed form. Read the compiled shape throughcalculation, and treatexpressionas the authored definition. getName()has been removed from field objects. UseAgWidgetApi.getFieldName(field)instead, which resolves the display name for any field, including a user rename and an implicit measure's aggregation label.FieldSample,SampleValueandSampleOptionshave been removed from the package entry point with no replacement. Nothing else in the public API referenced them.Spreadablehas been removed from the package entry point, and the type it fed has been rewritten. SeeAgWidgetFormFormatIDunder Updated APIs below.createTitleGroup,createSubtitleGroupandcreateCaptionGrouphave been removed fromAgWidgetFormParams. Pass your own groups and controls tocreateTitleSection()instead, through itsitemsbuilder, which appends them after the built-in title, subtitle and caption groups.
createTitleSection(() => [myGroup]); Renamed APIs Copy Link
The chart series configuration interfaces have been renamed from …SeriesConfig to …SeriesTheme, for consistency with the other chart theme types. Only the names have changed; the shape of each type is the same. Update any imports that reference the old names:
AgAreaChartSeriesConfig→AgAreaChartSeriesThemeAgBubbleChartSeriesConfig→AgBubbleChartSeriesThemeAgLineChartSeriesConfig→AgLineChartSeriesThemeAgNightingaleChartSeriesConfig→AgNightingaleChartSeriesThemeAgRadarAreaChartSeriesConfig→AgRadarAreaChartSeriesThemeAgRadarLineChartSeriesConfig→AgRadarLineChartSeriesThemeAgRadialBarChartSeriesConfig→AgRadialBarChartSeriesThemeAgRadialColumnChartSeriesConfig→AgRadialColumnChartSeriesThemeAgScatterChartSeriesConfig→AgScatterChartSeriesTheme
The AI schema context types have gained an AgAi prefix, again for consistency. Only the names have changed:
SchemaMeta→AgAiSchemaMetaTableMeta→AgAiTableMetaColumnMeta→AgAiColumnMetaMeasureMeta→AgAiMeasureMetaCalculatedMeta→AgAiCalculatedMetaFieldMeta→AgAiFieldMetaRelationshipMeta→AgAiRelationshipMetaVocabularyMeta→AgAiVocabularyMetaFragmentsMeta→AgAiFragmentsMetaWidgetEntry→AgAiWidgetEntryWidgetSizing→AgAiWidgetSizing
Updated APIs Copy Link
AgWidgetToolbarButton.iconis now typedAgStudioIcon. If changing the icon for a widget toolbar item, use the raw icon value instead. See Icons for the list of icon values.AgExpressionFieldDefinition.expressionnow accepts a string as well as a parsed expression, so an expression can be authored as text. Code that writes one is unaffected; code that reads one must handle both forms.AgEditableFieldKeyshas gained a requiredexpression: booleankey. A hand-writtenAgEditableFieldKeysliteral needs that key added.AgWidgetFormFormatIDnow reads`format.${'style' | 'title' | 'subtitle' | 'caption' | 'crossFilter'}.${string}`. It has gainedformat.caption.*.AgBaseField,AgColumn,AgCalculatedColumn,AgMeasure,AgImplicitMeasure,AgField,AgWidgetFieldandAgBaseFieldDefinitionhave gained a third generic parameter for a field'scontext. It is defaulted, so ordinary use is unaffected; a mapped or conditional type written over the exact parameter list may need updating.AgPivotCellKeySpec.fnnow receives a fifth argument,isOthers, appended after the existingcontextargument so a four-argument function keeps readingcontextfrom the same position. A bucketed dimension substitutesnullinto the same tuple position for both the aggregated Other bucket and a member whose value is null, so a key function that ignores the new argument gives the two the same key.
Custom Data Engines Copy Link
These changes keep their existing types and change what a custom AgDataEngine or data source must do. The fan-out and anchor checks below are the exception: they apply whatever engine executes the query, so a dashboard on Studio's own engine sees those too.
- Each request in a call must now resolve independently. Studio collects every widget's request for a render cycle into a single
execute()orexecuteCube()call, so a call carries more requests than it did before. The requests come from unrelated callers, so one request's failure or cancellation must be reported as that request's own result and must not reject the promise returned for the whole call. An engine that rejects the call will hand empty results to every unrelated widget batched into it rather than raising an error. AgRequestOptions.signalis now honoured. Studio aborts it when a request is superseded or times out, and discards any result that arrives after that. An engine can now be stopped mid-request where it never was before, and should propagate the signal into its own backend call.- Grid widget rows must carry a unique, stable identifier. A source that already carries its own identifier should name it as
rowIdFieldin the result metadata, so Studio uses it directly rather than computing one. - Fan-out detection and the many-to-many anchor refusal now run for every query shape and every engine, including chart and native widget queries and any query resolved through
resolveApiQuery. A query whose joins inflate its aggregates now reports a finding instead of silently inflated numbers. The anchor refusal can now reject a query it never reached before. A query can declare intensional duplication, throughacceptFanouton the join clause. - Drill-across is also no longer dispatched to a caller-supplied engine which cannot carry it out. Such a query now reports its fan-out.
data.options.fanout.executedefaults to'allow', so the query still runs its flat join and can still return inflated aggregates alongside the warning. Setexecute: 'prevent'to reject such a query instead: it fails with a validation error and returns no data.- The base source of a query is now explicit.
AgJoinClausecarriesleftSourceId, naming the source a join attaches to, andleftSourceAliasto disambiguate a self-join. A query's base source isjoins[0].leftSourceId, falling back tojoins[0].leftField.sourceIdfor a hand-authored clause that omits it. An engine that derived the base source for itself should read it from the clause. - A bucketed dimension's Other bucket now has an explicit marker.
AgPivotColumnAxisEntryandAgPivotColumnNodecarryisOthers, matchingAgResultTuple. The bucket and a member whose value is null both present the samenull, so a consumer telling the two apart must check this flag rather than testing the tuple's value againstnull. - A source that declares
capabilitiesis called differently.getDatareceives a thirdoptionsargument carrying one or more ofpaging,filterandsort. The engine trusts the response for a capability. Other capabilities are then provided by the built-in engine.
Theming Copy Link
Removed Theme Parameters Copy Link
The following parameters have been removed from AgStudioThemeParams. Studio never applied them, so setting them never changed anything on screen. Delete them from your theme; there is no replacement, because there was nothing to replace.
cardShadowchartBorderRadiuschartButtonFontWeightchartChromeBackgroundColorchartChromeFontFamilychartChromeFontSizechartChromeFontWeightchartChromeSubtleTextColorchartChromeTextColorchartForegroundColorchartGroupedCategoryLineColorchartInputBackgroundColorchartInputBorderchartInputBorderRadiuschartInputTextColorchartPaletteDownFillColorchartPaletteDownStrokeColorchartPaletteNeutralFillColorchartPaletteNeutralStrokeColorchartPaletteUpFillColorchartPaletteUpStrokeColorchartTooltipSubtleTextColorchromeBackgroundColordragAndDropImageBackgroundColordragAndDropImageBorderdragAndDropImageNotAllowedBorderfocusErrorShadowgridAutoHeightMinBodyHeightgridBackgroundColorgridBorderColorgridBorderRadiusgridBorderWidthgridCardShadowgridCellEditingBordergridCellEditingShadowgridCellHorizontalPaddingScalegridCellWidgetSpacinggridChromeBackgroundColorgridColumnDragIndicatorColorgridColumnDragIndicatorWidthgridColumnDropCellBackgroundColorgridColumnDropCellBordergridColumnDropCellDragHandleColorgridColumnDropCellTextColorgridColumnHoverColorgridColumnSelectIndentSizegridDialogBordergridDialogShadowgridDragAndDropImageBackgroundColorgridDragAndDropImageBordergridDragAndDropImageNotAllowedBordergridDragAndDropImageShadowgridDragHandleColorgridDropdownShadowgridFindActiveMatchBackgroundColorgridFindActiveMatchColorgridFindMatchBackgroundColorgridFindMatchColorgridFocusErrorShadowgridFocusShadowgridFooterRowBordergridForegroundColorgridFullRowEditInvalidBackgroundColorgridHeaderCellBackgroundTransitionDurationgridHeaderCellHoverBackgroundColorgridHeaderCellMovingBackgroundColorgridHeaderColumnBordergridHeaderColumnResizeHandleColorgridHeaderColumnResizeHandleHeightgridHeaderColumnResizeHandleWidthgridHeaderVerticalPaddingScalegridIconButtonActiveBackgroundColorgridIconButtonActiveColorgridIconButtonActiveIndicatorColorgridIconButtonBackgroundColorgridIconButtonBackgroundSpreadgridIconButtonBorderRadiusgridIconButtonColorgridIconButtonHoverBackgroundColorgridIconButtonHoverColorgridInvalidColorgridListItemHeightgridMenuBackgroundColorgridMenuBordergridMenuSeparatorColorgridMenuShadowgridMenuTextColorgridModalOverlayBackgroundColorgridPaginationPanelHeightgridPanelBackgroundColorgridPickerFieldHeightgridPinnedColumnBordergridPinnedRowBackgroundColorgridPinnedRowFontWeightgridPinnedRowTextColorgridPinnedSourceRowBackgroundColorgridPinnedSourceRowFontWeightgridPinnedSourceRowTextColorgridPopupShadowgridRangeHeaderHighlightColorgridRangeSelectionBackgroundColorgridRangeSelectionBorderColorgridRangeSelectionBorderStylegridRangeSelectionChartBackgroundColorgridRangeSelectionChartCategoryBackgroundColorgridRangeSelectionHighlightColorgridRowDragIndicatorColorgridRowDragIndicatorWidthgridRowGroupIndentSizegridRowLoadingSkeletonEffectColorgridRowNumbersSelectedColorgridRowVerticalPaddingScalegridSelectCellBackgroundColorgridSelectCellBordergridSelectedRowBackgroundColorgridSubtleTextColorgridTextColorgridToggleButtonHeightgridToggleButtonOffBackgroundColorgridToggleButtonOnBackgroundColorgridToggleButtonSwitchBackgroundColorgridToggleButtonSwitchInsetgridToggleButtonWidthgridTooltipBackgroundColorgridTooltipBordergridTooltipErrorBackgroundColorgridTooltipErrorBordergridTooltipErrorTextColorgridTooltipTextColorgridValueChangeDeltaDownColorgridValueChangeDeltaUpColorgridValueChangeValueHighlightBackgroundColorgridWidgetContainerHorizontalPaddinggridWidgetContainerVerticalPaddinggridWidgetHorizontalSpacinggridWidgetVerticalSpacinggridWrapperBackgroundColorheaderBackgroundColorheaderHeightheaderVerticalPaddingScalestudioAiPanelFieldBadgeBackgroundColorstudioAiPanelFieldBadgeColorstudioAiPanelWidgetBadgeBackgroundColorstudioAiPanelWidgetBadgeColorstudioPanelBackgroundColortabSelectedUnderlineColortabSelectedUnderlineTransitionDurationtabSelectedUnderlineWidthtooltipErrorBackgroundColortooltipErrorBordertooltipErrorTextColor
The four AI panel badge parameters are a slightly different case from the rest of this list: they did once apply. Inline field and widget references in the AI panel are now distinguished by icon rather than by colour, and take their colours from the surrounding theme, so there is no badge left for these to colour.
Renamed Theme Parameters Copy Link
These theme params have been renamed so that each name describes what it actually affects. The corresponding CSS custom property changes with it, so application stylesheets that read the generated --ag- variables need updating too:
| Old Param | New Param |
|---|---|
headerFontFamily( --ag-header-font-family) | studioPanelHeaderFontFamily( --ag-studio-panel-header-font-family) |
headerFontSize( --ag-header-font-size) | studioPanelHeaderFontSize( --ag-studio-panel-header-font-size) |
headerFontWeight( --ag-header-font-weight) | studioPanelHeaderFontWeight( --ag-studio-panel-header-font-weight) |
headerLineHeight( --ag-header-line-height) | studioPanelHeaderLineHeight( --ag-studio-panel-header-line-height) |
headerTextColor( --ag-header-text-color) | studioWidgetTitleTextColor( --ag-studio-widget-title-text-color) |
studioChartLabelColor( --ag-studio-chart-label-color) | studioLinearGaugeLabelColor( --ag-studio-linear-gauge-label-color) |
studioFiltersPanelCardSubtleHoverColor( --ag-studio-filters-panel-card-subtle-hover-color) | studioFiltersPanelCardIconSubtleHoverColor( --ag-studio-filters-panel-card-icon-subtle-hover-color) |
studioPanelGroupTitleBarBackgroundColor( --ag-studio-panel-group-title-bar-background-color) | studioPanelGroupTitleBarTextColor( --ag-studio-panel-group-title-bar-text-color) |
studioPanelTabHeaderBackgroundColor( --ag-studio-panel-tab-header-background-color) | tabBarBackgroundColor( --ag-tab-bar-background-color) |
studioPanelTabHeaderSelectedBackgroundColor( --ag-studio-panel-tab-header-selected-background-color) | tabSelectedBackgroundColor( --ag-tab-selected-background-color) |
studioPanelTabHeaderSelectedShadow( --ag-studio-panel-tab-header-selected-shadow) | tabSelectedShadow( --ag-tab-selected-shadow) |
studioWidgetSelectIconBackgroundColor( --ag-studio-widget-select-icon-background-color) | studioWidgetSelectionItemBackgroundColor( --ag-studio-widget-selection-item-background-color) |
studioWidgetSelectIconBorder( --ag-studio-widget-select-icon-border) | studioWidgetSelectionItemBorder( --ag-studio-widget-selection-item-border) |
studioWidgetTileHeight( --ag-studio-widget-tile-height) | studioWidgetSelectionItemHeight( --ag-studio-widget-selection-item-height) |
studioWidgetTileWidth( --ag-studio-widget-tile-width) | studioWidgetSelectionItemWidth( --ag-studio-widget-selection-item-width) |
widgetContainerHorizontalPadding( --ag-widget-container-horizontal-padding) | studioFiltersPanelContainerHorizontalPadding( --ag-studio-filters-panel-container-horizontal-padding) |
widgetContainerVerticalPadding( --ag-widget-container-vertical-padding) | studioFiltersPanelContainerVerticalPadding( --ag-studio-filters-panel-container-vertical-padding) |
widgetHorizontalSpacing( --ag-widget-horizontal-spacing) | studioFiltersPanelItemHorizontalSpacing( --ag-studio-filters-panel-item-horizontal-spacing) |
widgetVerticalSpacing( --ag-widget-vertical-spacing) | studioFiltersPanelItemVerticalSpacing( --ag-studio-filters-panel-item-vertical-spacing) |
Only the names have changed. Each param keeps its previous value and effect, so the rendered UI is unchanged once the new names are in place.
Theme Type Changes Copy Link
AgStudioThemeParamsnow directly includes the properties inOptionalThemeParams.OptionalThemeParamshas been removed as no longer needed.AgStudioDefaultThemeParamshas been removed. UseAgStudioThemeParamsinstead.
Behaviour Changes Copy Link
The full list of behaviour changes in AG Studio 3.
Dashboards and Widgets Copy Link
- Switching between Edit and View Mode no longer saves, discards or reapplies state. Changes made in View Mode now persist when returning to Edit Mode. Applications that relied on the reset can reproduce it by calling
getState()before switching to View Mode andsetState()when switching back - see Modes and State. - Text and image widgets now inherit the page level widget appearance settings like every other widget type. They previously never drew a border or corner radius, whatever the page was set to. To keep the previous look on an individual widget, turn its border off in the Widget Appearance section of the widget's format settings, or set
borderEnabled: falseandborderRadius: 0underformat.widgetin saved state. Saved state is not migrated for this change, so an existing dashboard that enables page level widget borders will start showing borders on its text and image widgets; dashboards that leave page level widget borders off, which is the default, look identical.
Cross Filter Interaction Copy Link
- Cross filtering from a chart widget with a legend field now filters on the category and the legend value together. Clicking a point or segment previously applied a condition on the category field alone; it now applies a single combined condition matching the clicked category and its legend series. A combined condition is shown as a single read-only card in the Cross Filters section of the Filters Panel, rather than one card per field. Charts with no legend field are unaffected.
- The Cross Highlight cross filtering mode has been removed for 100% Stacked Bar and 100% Stacked Column widgets, because a proportional highlight cannot be drawn on a normalised stack. Those two widgets now offer Cross Filter and None, and a new one defaults to Cross Filter. A report saved before 3.0.0 is migrated when it loads, so a 100% stacked widget set to Cross Highlight becomes Cross Filter and redraws under the condition instead of highlighting within it. State you author yourself is not migrated, so set
format.crossFilterto'filter'there. Every other bar, column, pie and doughnut widget keeps the option and still defaults to it.
Charts Copy Link
- A chart that splits its series by a legend field now limits how many series it draws. Legend members are ranked by the first plotted measure and the rest are dropped, where a chart whose legend produced more series than the chart's own limit previously failed to render at all. The budget counts series rather than members, so two plotted measures halve how many members fit. Set
format.seriesLimit.maxto choose your own budget, orformat.seriesLimit.enabled: falseto plot every member.
Grid Widget Copy Link
- A grid widget whose row request fails now reports the failure through Studio's error handling and shows its no-data state. It previously logged to the console and left the widget under its loading overlay, so a failed request looked like one still in flight.
- The server-side grid widget now bounds how many row blocks it keeps and how many row requests it runs at once. Sustained scrolling refetches evicted blocks where it previously served them from a cache that grew without limit. Set
maxBlocksInCacheinAgGridWidgetOptionsto choose your own bound.