@@ -1193,8 +1193,8 @@ func (e *Executor) applyInsertWidgetWith(rawData bson.D, op *ast.InsertWidgetOp,
11931193 // Find entity context from enclosing DataView/DataGrid/ListView
11941194 entityCtx := findEnclosingEntityContext (rawData , op .Target .Widget )
11951195
1196- // Build new widget BSON from AST
1197- newBsonWidgets , err := e .buildWidgetsBson (op .Widgets , moduleName , moduleID , entityCtx )
1196+ // Build new widget BSON from AST (pass rawData for page param + widget scope resolution)
1197+ newBsonWidgets , err := e .buildWidgetsBson (op .Widgets , moduleName , moduleID , entityCtx , rawData )
11981198 if err != nil {
11991199 return fmt .Errorf ("failed to build widgets: %w" , err )
12001200 }
@@ -1281,8 +1281,8 @@ func (e *Executor) applyReplaceWidgetWith(rawData bson.D, op *ast.ReplaceWidgetO
12811281 // Find entity context from enclosing DataView/DataGrid/ListView
12821282 entityCtx := findEnclosingEntityContext (rawData , op .Target .Widget )
12831283
1284- // Build new widget BSON from AST
1285- newBsonWidgets , err := e .buildWidgetsBson (op .NewWidgets , moduleName , moduleID , entityCtx )
1284+ // Build new widget BSON from AST (pass rawData for page param + widget scope resolution)
1285+ newBsonWidgets , err := e .buildWidgetsBson (op .NewWidgets , moduleName , moduleID , entityCtx , rawData )
12861286 if err != nil {
12871287 return fmt .Errorf ("failed to build replacement widgets: %w" , err )
12881288 }
@@ -1529,16 +1529,21 @@ func applyDropVariable(rawData bson.D, op *ast.DropVariableOp) error {
15291529
15301530// buildWidgetsBson converts AST widgets to ordered BSON documents.
15311531// Returns bson.D elements (not map[string]any) to preserve field ordering.
1532- func (e * Executor ) buildWidgetsBson (widgets []* ast.WidgetV3 , moduleName string , moduleID model.ID , entityContext string ) ([]any , error ) {
1532+ // rawPageData is the full page/snippet BSON, used to extract page parameters
1533+ // and existing widget IDs for PARAMETER/SELECTION DataSource resolution.
1534+ func (e * Executor ) buildWidgetsBson (widgets []* ast.WidgetV3 , moduleName string , moduleID model.ID , entityContext string , rawPageData bson.D ) ([]any , error ) {
1535+ paramScope , paramEntityNames := extractPageParamsFromBSON (rawPageData )
1536+ widgetScope := extractWidgetScopeFromBSON (rawPageData )
1537+
15331538 pb := & pageBuilder {
15341539 writer : e .writer ,
15351540 reader : e .reader ,
15361541 moduleID : moduleID ,
15371542 moduleName : moduleName ,
15381543 entityContext : entityContext ,
1539- widgetScope : make ( map [ string ]model. ID ) ,
1540- paramScope : make ( map [ string ]model. ID ) ,
1541- paramEntityNames : make ( map [ string ] string ) ,
1544+ widgetScope : widgetScope ,
1545+ paramScope : paramScope ,
1546+ paramEntityNames : paramEntityNames ,
15421547 execCache : e .cache ,
15431548 fragments : e .fragments ,
15441549 themeRegistry : e .getThemeRegistry (),
@@ -1561,6 +1566,150 @@ func (e *Executor) buildWidgetsBson(widgets []*ast.WidgetV3, moduleName string,
15611566 return result , nil
15621567}
15631568
1569+ // extractPageParamsFromBSON extracts page/snippet parameter names and entity
1570+ // IDs from the raw BSON document. This enables ALTER PAGE REPLACE/INSERT
1571+ // operations to resolve PARAMETER DataSource references (e.g., DataSource: $Customer).
1572+ func extractPageParamsFromBSON (rawData bson.D ) (map [string ]model.ID , map [string ]string ) {
1573+ paramScope := make (map [string ]model.ID )
1574+ paramEntityNames := make (map [string ]string )
1575+ if rawData == nil {
1576+ return paramScope , paramEntityNames
1577+ }
1578+
1579+ params := dGetArrayElements (dGet (rawData , "Parameters" ))
1580+ for _ , p := range params {
1581+ pDoc , ok := p .(bson.D )
1582+ if ! ok {
1583+ continue
1584+ }
1585+ name := dGetString (pDoc , "Name" )
1586+ if name == "" {
1587+ continue
1588+ }
1589+ paramType := dGetDoc (pDoc , "ParameterType" )
1590+ if paramType == nil {
1591+ continue
1592+ }
1593+ typeName := dGetString (paramType , "$Type" )
1594+ if typeName != "DataTypes$ObjectType" {
1595+ continue
1596+ }
1597+ entityName := dGetString (paramType , "Entity" )
1598+ if entityName == "" {
1599+ continue
1600+ }
1601+ idVal := dGet (pDoc , "$ID" )
1602+ paramID := model .ID (extractBinaryIDFromDoc (idVal ))
1603+ paramScope [name ] = paramID
1604+ paramEntityNames [name ] = entityName
1605+ }
1606+ return paramScope , paramEntityNames
1607+ }
1608+
1609+ // extractWidgetScopeFromBSON walks the entire raw BSON widget tree and
1610+ // collects a map of widget name → widget ID. This enables ALTER PAGE INSERT
1611+ // operations to resolve SELECTION DataSource references to existing sibling widgets.
1612+ func extractWidgetScopeFromBSON (rawData bson.D ) map [string ]model.ID {
1613+ scope := make (map [string ]model.ID )
1614+ if rawData == nil {
1615+ return scope
1616+ }
1617+ // Page format: FormCall.Arguments[].Widgets[]
1618+ if formCall := dGetDoc (rawData , "FormCall" ); formCall != nil {
1619+ args := dGetArrayElements (dGet (formCall , "Arguments" ))
1620+ for _ , arg := range args {
1621+ argDoc , ok := arg .(bson.D )
1622+ if ! ok {
1623+ continue
1624+ }
1625+ collectWidgetScope (argDoc , "Widgets" , scope )
1626+ }
1627+ }
1628+ // Snippet format: Widgets[] or Widget.Widgets[]
1629+ collectWidgetScope (rawData , "Widgets" , scope )
1630+ if widgetContainer := dGetDoc (rawData , "Widget" ); widgetContainer != nil {
1631+ collectWidgetScope (widgetContainer , "Widgets" , scope )
1632+ }
1633+ return scope
1634+ }
1635+
1636+ // collectWidgetScope recursively walks a widget array and collects name→ID mappings.
1637+ func collectWidgetScope (parentDoc bson.D , key string , scope map [string ]model.ID ) {
1638+ elements := dGetArrayElements (dGet (parentDoc , key ))
1639+ for _ , elem := range elements {
1640+ wDoc , ok := elem .(bson.D )
1641+ if ! ok {
1642+ continue
1643+ }
1644+ name := dGetString (wDoc , "Name" )
1645+ if name != "" {
1646+ idVal := dGet (wDoc , "$ID" )
1647+ if wID := extractBinaryIDFromDoc (idVal ); wID != "" {
1648+ scope [name ] = model .ID (wID )
1649+ }
1650+ }
1651+ // Also register entity context for widgets with DataSource
1652+ // so SELECTION can resolve the entity type
1653+ collectWidgetScopeInChildren (wDoc , scope )
1654+ }
1655+ }
1656+
1657+ // collectWidgetScopeInChildren recursively walks widget children to collect scope.
1658+ func collectWidgetScopeInChildren (wDoc bson.D , scope map [string ]model.ID ) {
1659+ typeName := dGetString (wDoc , "$Type" )
1660+
1661+ // Direct Widgets[]
1662+ collectWidgetScope (wDoc , "Widgets" , scope )
1663+ // FooterWidgets[]
1664+ collectWidgetScope (wDoc , "FooterWidgets" , scope )
1665+ // LayoutGrid: Rows[].Columns[].Widgets[]
1666+ if strings .Contains (typeName , "LayoutGrid" ) {
1667+ rows := dGetArrayElements (dGet (wDoc , "Rows" ))
1668+ for _ , row := range rows {
1669+ rowDoc , ok := row .(bson.D )
1670+ if ! ok {
1671+ continue
1672+ }
1673+ cols := dGetArrayElements (dGet (rowDoc , "Columns" ))
1674+ for _ , col := range cols {
1675+ colDoc , ok := col .(bson.D )
1676+ if ! ok {
1677+ continue
1678+ }
1679+ collectWidgetScope (colDoc , "Widgets" , scope )
1680+ }
1681+ }
1682+ }
1683+ // TabContainer: TabPages[].Widgets[]
1684+ tabPages := dGetArrayElements (dGet (wDoc , "TabPages" ))
1685+ for _ , tp := range tabPages {
1686+ tpDoc , ok := tp .(bson.D )
1687+ if ! ok {
1688+ continue
1689+ }
1690+ collectWidgetScope (tpDoc , "Widgets" , scope )
1691+ }
1692+ // ControlBar
1693+ if controlBar := dGetDoc (wDoc , "ControlBar" ); controlBar != nil {
1694+ collectWidgetScope (controlBar , "Items" , scope )
1695+ }
1696+ // CustomWidget (pluggable): Object.Properties[].Value.Widgets[]
1697+ if strings .Contains (typeName , "CustomWidget" ) {
1698+ if obj := dGetDoc (wDoc , "Object" ); obj != nil {
1699+ props := dGetArrayElements (dGet (obj , "Properties" ))
1700+ for _ , prop := range props {
1701+ propDoc , ok := prop .(bson.D )
1702+ if ! ok {
1703+ continue
1704+ }
1705+ if valDoc := dGetDoc (propDoc , "Value" ); valDoc != nil {
1706+ collectWidgetScope (valDoc , "Widgets" , scope )
1707+ }
1708+ }
1709+ }
1710+ }
1711+ }
1712+
15641713// ============================================================================
15651714// Helper: SerializeWidget is already available via mpr package
15661715// ============================================================================
0 commit comments