I’m sure you guys don’t need this but here is a Claue investigation tha tclearly defines the problem and a possible resolution.
Bug Report — Bricks Builder
## Title
Component **Select** property loses its auto-populated options when linked to a **child component’s** property (nested components)
## Environment
- **Bricks version:** 2.3.7
- **Area:** Builder — Components → Component Properties → Select control option resolution
- **Scope:** Builder UI only (client-side Vue app). No server/render involvement.
-–
## Summary
When a component **Select** property is bound directly to a **native element control** whose control defines an options array (e.g. a Heading’s `tag` control → `h1…h6`), the Select auto-populates its options correctly.
When that component is **nested inside a parent component**, and you create a Select property on the **parent** that links to the **child component’s** property, the option list is **not** propagated. The parent’s Select renders **empty**.
The option resolver only knows how to read options from a *native element control*. It has no case for a connection that points at *another component property*, so it never follows the link down to the underlying control’s options array.
-–
## Steps to Reproduce
1. Create a **Component A** containing a single **Heading** element.
2. On Component A, add a **Component Property** of type **Select** and link it to the Heading’s **`tag`** property.
The Select auto-populates with `h1, h2, h3, h4, h5, h6`. **Works.**
3. Create a **Component B** and place an instance of **Component A** inside it.
4. On Component B, add a **Component Property** of type **Select** and link it to **Component A’s** Select property (created in step 2).
The Select does **not** auto-populate. The option list is empty.
### Expected
The parent (Component B) Select inherits the same option list (`h1…h6`) by following the link chain down to the Heading’s `tag` control.
### Actual
The parent Select has no options.
-–
## Root Cause (technical)
A component property’s binding is stored in `property.connections` as a map `{ [elementId]: [connectionKey] }`. The `connectionKey` takes **two forms**:
1. **Native element control** — a bare control key, e.g. `[“tag”]`.
2. **Component-property link** — a string `“parent:cid_:prop_”` (written by `setPropertyConnection`, parsed elsewhere by the regex `^parent:cid_([^:]+):prop_([^:]+)$`).
The Select option resolver — the **`propertyControlOptions(e)`** method in the builder app (`assets/js/main.min.js`) — only handles **form 1**. Reconstructed logic:
```js
propertyControlOptions(e) {
let n = e?.options || null;
if (n) return n; // static options
if (e?.connections) {
const o = Object.keys(e.connections)\[0\]; *// target element id*
const a = this.connectedComponent?.elements.find(*el* => el.id === o);
const s = this.$\_getElementConfig(a?.name); *// NATIVE element config ONLY*
const l = e.connections\[o\]; *// \["tag"\] OR \["parent:cid\_..:prop\_.."\]*
const c = s?.controls?.\[l\]?.options; *// reads native control's options*
if (c) n = c;
}
return n;
}
```
`$_getElementConfig(name)` resolves a **registered native element** by name (e.g. `‘heading’`) and exposes `controls.tag.options`. That’s why the single-level case works.
In the nested case, the parent property’s connection target is the **child component instance element** (which has a `.cid` and is **not** a native element), and/or `l` is the link string `“parent:cid_…:prop_…”`. Either way:
- `$_getElementConfig(a.name)` has no matching `controls[l]`, and
- `s.controls[]` is `undefined`,
so `c` is `undefined`, `n` stays empty, and the Select renders with no options. **The resolver never follows the link into the child component’s `properties[]` to read its options (or recurse to its own connection).**
The editing-side twin — the `propertyControl` computed (the `“select”` branch, same file) — has the **identical defect**, so the property-definition dropdown is affected the same way.
-–
## Suggested Fix
Add a recursion step to `propertyControlOptions` (and the matching `propertyControl` computed): when the bound target is another component property (link string `parent:cid_…:prop_…`, or a plain key on a component-instance element with a `.cid`), look up the child component, find the property, and resolve **its** options — preferring a static `options[]` if defined, otherwise following its own connection to a native control’s options, recursing through multiple nesting levels with a cycle guard.
```js
// New helper — resolves a component property’s option list, recursing through
// component-property links until it reaches a native control’s options or a static list.
$_resolveComponentPropertyOptions(cid, propId, seen = {}) {
const guard = cid + ‘:’ + propId;
if (seen[guard]) return null; // circular-link guard
seen[guard] = true;
const comp = this.$_getComponentById(cid);
if (!comp) return null;
const prop = (comp.properties || []).find(p => p.id === propId);
if (!prop) return null;
// 1. Child property defines its own static options.
if (Array.isArray(prop.options) && prop.options.length) return prop.options;
// 2. Child property is itself bound — resolve its connection.
if (prop.connections) {
const o = Object.keys(prop.connections)\[0\];
const keys = prop.connections\[o\] || \[\];
const l = Array.isArray(keys) ? keys\[0\] : keys;
const el = (comp.elements || \[\]).find(*x* => x.id === o);
*// 2a. Bound to a native control inside the child component.*
const opts = this.$\_getElementConfig(el?.name)?.controls?.\[l\]?.options;
if (opts) return opts;
*// 2b. Bound to a deeper component property — recurse.*
const m = typeof l === 'string' && l.match(/^parent:cid\_(\[^:\]+):prop\_(\[^:\]+)$/);
if (m) return this.$\_resolveComponentPropertyOptions(m\[1\], m\[2\], seen);
if (el?.cid) return this.$\_resolveComponentPropertyOptions(el.cid, l, seen);
}
return null;
}
```
Then in `propertyControlOptions`, when the native lookup yields nothing, fall through to the helper:
```js
if (!c) {
let childCid = null, childPropId = null;
const m = typeof l === ‘string’ && l.match(/^parent:cid_([^:]+):prop_([^:]+)$/);
if (m) { childCid = m[1]; childPropId = m[2]; }
else if (a?.cid) { childCid = a.cid; childPropId = l; }
if (childCid && childPropId) c = this.$_resolveComponentPropertyOptions(childCid, childPropId);
}
```
### Edge cases covered
- **Multi-level nesting** (component → component → component): handled by recursion following each link.
- **Circular property links:** guarded by the `seen` set (the existing server-side nesting guard in `process_components_recursive` does not cover the property-link graph).
- **Static vs. derived options:** static author-defined `options[]` on the child takes precedence; only an unstyled child Select falls through to follow its control binding.
- **`multiple` / `searchable`:** only `options` is propagated. If a parent Select should also inherit `multiple`, propagate `prop.multiple` alongside.
-–
## Notes
- The fix belongs in the upstream builder Vue source (the method/computed compile into `assets/js/main.min.js`). Minified symbol names are unstable per build, so this should be patched at source.
- There is currently **no PHP filter or builder-JS hook** that intercepts component-property option resolution, so third parties cannot fix this non-destructively — which is why we’re reporting it upstream rather than working around it.