Skip to content

Commit 30dbb6e

Browse files
committed
fix(fields): rebase caller sub-input ids onto the generated field id
For unknown/third-party compound fields, gf_add_field preserved caller inputs verbatim, so dotted sub-input ids kept their assumed parent (e.g. 9.1) instead of the id generateFieldId assigns, orphaning the sub-inputs. Rebase dotted ids onto the generated field id (mirrors assignFieldIds); known compound types keep generateSubInputs unchanged. Live-verified on dev.test: 99.x inputs persist as 4.x. Claude-Session: https://claude.ai/code/session_01LJHfTpQknHFs1j5ayj7fpo
1 parent fea0977 commit 30dbb6e

2 files changed

Lines changed: 47 additions & 4 deletions

File tree

src/field-operations/field-manager.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,15 @@ export class FieldManager {
4444
// Create field with type-specific defaults (none for unknown types)
4545
const field = this.createField(fieldId, fieldType, properties, fieldDef || {});
4646

47-
// Generate compound sub-inputs only for known compound types (address.1,
48-
// name.3, …). Unknown types keep any caller-supplied `inputs` untouched.
47+
// Known compound types (address, name, …) regenerate sub-inputs from the
48+
// registry, keyed off the generated field id. Otherwise caller-supplied
49+
// `inputs` are kept, but their dotted sub-input ids are rebased onto the
50+
// generated field id so the parent reference matches (mirrors assignFieldIds).
4951
const isCompoundType = fieldDef?.storage?.type === 'compound';
5052
if (isCompoundType) {
5153
field.inputs = this.generateSubInputs(field, fieldDef);
54+
} else if (Array.isArray(field.inputs)) {
55+
field.inputs = this.rebaseSubInputIds(field.inputs, fieldId);
5256
}
5357

5458
// Normalize layout grid properties (layoutGroupId, layoutGridColumnSpan)
@@ -202,6 +206,26 @@ export class FieldManager {
202206
return maxId + 1;
203207
}
204208

209+
/**
210+
* Rebase dotted sub-input ids (e.g. "9.1") onto a new parent field id so each
211+
* sub-input's parent reference matches the field it belongs to. Non-dotted and
212+
* non-string ids pass through. Mirrors assignFieldIds in the field registry.
213+
*
214+
* @param {Array<object>} inputs
215+
* @param {number|string} baseId
216+
* @returns {Array<object>}
217+
*/
218+
rebaseSubInputIds(inputs, baseId) {
219+
return inputs.map((input) => {
220+
const hasDottedId = input && typeof input.id === 'string' && input.id.includes('.');
221+
if (!hasDottedId) {
222+
return input;
223+
}
224+
const sub = input.id.slice(input.id.indexOf('.') + 1);
225+
return { ...input, id: `${baseId}.${sub}` };
226+
});
227+
}
228+
205229
/**
206230
* Create field with intelligent defaults from registry
207231
*/

test/field-manager.test.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,23 +259,42 @@ test('FieldManager - addField', async (t) => {
259259
);
260260
});
261261

262-
await t.test('preserves caller-supplied inputs for an unknown compound-like type', async () => {
262+
await t.test('rebases caller-supplied dotted sub-input ids onto the generated field id (unknown type)', async () => {
263263
const apiClient = createMockApiClient();
264264
const registry = createMockRegistry();
265265
const validator = createMockValidator();
266266
const manager = new FieldManager(apiClient, registry, validator);
267267

268+
// The caller guessed parent id 9, but the form's next id is 4. Sub-inputs
269+
// must follow the generated field id (4.x), not stay orphaned at 9.x. Labels
270+
// and other props are preserved.
268271
const result = await manager.addField(1, 'custom_compound', {
269272
label: 'Custom Compound',
270-
inputs: [{ id: '4.1', label: 'Part A' }, { id: '4.2', label: 'Part B' }]
273+
inputs: [{ id: '9.1', label: 'Part A' }, { id: '9.2', label: 'Part B' }]
271274
});
272275

273276
assert.strictEqual(result.success, true);
277+
assert.strictEqual(result.field.id, 4);
274278
assert.deepStrictEqual(result.field.inputs, [
275279
{ id: '4.1', label: 'Part A' },
276280
{ id: '4.2', label: 'Part B' }
277281
]);
278282
});
283+
284+
await t.test('known compound type still gets registry sub-inputs keyed on the generated field id', async () => {
285+
const apiClient = createMockApiClient();
286+
const registry = createMockRegistry();
287+
const validator = createMockValidator();
288+
const manager = new FieldManager(apiClient, registry, validator);
289+
290+
const result = await manager.addField(1, 'address', { label: 'Mailing Address' });
291+
292+
assert.strictEqual(result.field.id, 4);
293+
assert.deepStrictEqual(
294+
result.field.inputs.map((i) => i.id),
295+
['4.1', '4.2', '4.3', '4.4', '4.5', '4.6']
296+
);
297+
});
279298
});
280299

281300
test('FieldManager - updateField', async (t) => {

0 commit comments

Comments
 (0)