@@ -67,6 +67,51 @@ function mergeViewsIntoObjects(objects: any[], configs: any[]): any[] {
6767 } ) ;
6868}
6969
70+ // ---------------------------------------------------------------------------
71+ // Merge stack-level actions into object definitions.
72+ // Actions declared at the stack level (actions[]) need to be merged into
73+ // individual object definitions so the runtime protocol (and Console/Studio)
74+ // can render action buttons directly from objectDef.actions.
75+ // Matching uses explicit objectName on the action, or longest object-name
76+ // prefix of the action name (e.g. "account_send_email" → "account").
77+ // ---------------------------------------------------------------------------
78+ function mergeActionsIntoObjects ( objects : any [ ] , configs : any [ ] ) : any [ ] {
79+ const allActions : any [ ] = [ ] ;
80+ for ( const config of configs ) {
81+ if ( Array . isArray ( config . actions ) ) {
82+ allActions . push ( ...config . actions ) ;
83+ }
84+ }
85+ if ( allActions . length === 0 ) return objects ;
86+
87+ // Sort object names longest-first so "order_item" matches before "order"
88+ const objectNames = objects . map ( ( o : any ) => o . name as string )
89+ . sort ( ( a , b ) => b . length - a . length ) ;
90+
91+ const actionsByObject : Record < string , any [ ] > = { } ;
92+ for ( const action of allActions ) {
93+ let target : string | undefined = action . objectName ;
94+ if ( ! target ) {
95+ for ( const name of objectNames ) {
96+ if ( action . name . startsWith ( name + '_' ) ) {
97+ target = name ;
98+ break ;
99+ }
100+ }
101+ }
102+ if ( target ) {
103+ if ( ! actionsByObject [ target ] ) actionsByObject [ target ] = [ ] ;
104+ actionsByObject [ target ] . push ( action ) ;
105+ }
106+ }
107+
108+ return objects . map ( ( obj : any ) => {
109+ const actions = actionsByObject [ obj . name ] ;
110+ if ( ! actions ) return obj ;
111+ return { ...obj , actions : [ ...( obj . actions || [ ] ) , ...actions ] } ;
112+ } ) ;
113+ }
114+
70115const allConfigs = [ crmConfig , todoConfig , kitchenSinkConfig ] ;
71116
72117export const sharedConfig = {
@@ -81,12 +126,15 @@ export const sharedConfig = {
81126 // ============================================================================
82127 // Merged Stack Configuration (CRM + Todo + Kitchen Sink + Mock Metadata)
83128 // ============================================================================
84- objects : mergeViewsIntoObjects (
85- [
86- ...( crmConfig . objects || [ ] ) ,
87- ...( todoConfig . objects || [ ] ) ,
88- ...( kitchenSinkConfig . objects || [ ] ) ,
89- ] ,
129+ objects : mergeActionsIntoObjects (
130+ mergeViewsIntoObjects (
131+ [
132+ ...( crmConfig . objects || [ ] ) ,
133+ ...( todoConfig . objects || [ ] ) ,
134+ ...( kitchenSinkConfig . objects || [ ] ) ,
135+ ] ,
136+ allConfigs ,
137+ ) ,
90138 allConfigs ,
91139 ) ,
92140 apps : [
@@ -143,10 +191,13 @@ export const sharedConfig = {
143191} ;
144192
145193// defineStack() validates the config but strips non-standard properties like
146- // listViews from objects. Re-merge listViews after validation so the runtime
147- // protocol serves objects with their view definitions (calendar, kanban, etc.) .
194+ // listViews and actions from objects. Re-merge after validation so the runtime
195+ // protocol serves objects with their view and action definitions .
148196const validated = defineStack ( sharedConfig as Parameters < typeof defineStack > [ 0 ] ) ;
149197export default {
150198 ...validated ,
151- objects : mergeViewsIntoObjects ( validated . objects || [ ] , allConfigs ) ,
199+ objects : mergeActionsIntoObjects (
200+ mergeViewsIntoObjects ( validated . objects || [ ] , allConfigs ) ,
201+ allConfigs ,
202+ ) ,
152203} ;
0 commit comments