Repository navigation
Edit nested object/array properties in the namespaced property forms (#2027) - #2031
Conversation
…loses #2027) The recursive nested-property editor only reached the generic schema form, which persists flat at properties[<field>]. The task and per-type property forms share the same field dispatch but write under properties.task[<field>] and properties[<nodeType>][<field>], so the flat modal could not be dropped in — it would have written nested edits to the wrong place. Make the modal agnostic instead: it no longer touches the store, taking the current value and an onPersist callback from whichever form owns it. Each form supplies its own reader/writer, so all three share one editor and one modal while persisting to their own namespace. Both property forms now render leaves through the shared SchemaFieldLeaf rather than their own copies of the five-case dispatch. That removes both fall-throughs in one step: the "not yet implemented" boolean stub and "Unknown field type". Only the assignee combobox stays hand-rolled, having no shared equivalent. Two behaviors were preserved deliberately — the enum default fallback, and close-the-popover-on-date-pick, which moved into the shared leaf and so now also fixes the generic form and the nested editor, where the calendar used to stay open. The summary button is likewise shared, so a nested field looks the same in every form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review found a data-loss path that predates this PR but only becomes destructive now that nested values render here. `getPropertyValue` documented three supported shapes but implemented two: it never read the old flat location, even though `updateProperty` migrates from it on the first write. For a leaf that only meant a blank input the user retypes. For a nested object it meant the editor opened empty, and the first edit persisted that empty object over the real one — with `replaceProperties`, so the keys the user was never shown were simply gone. Reading the flat shape closes it, and matches both the docblock and the equivalent bridge the task form already has. Also from review: - Restored the empty-enum guard this form used to carry. It writes with `replaceProperties` and recomputes the title from the new value, so persisting an empty emission would blank both. Kept at the call site rather than in the shared leaf, since the other two forms never had it and may legitimately clear a value. - The shared enum trigger humanizes a stored value the schema no longer declares, instead of rendering the raw key — the collapsed header already humanizes it, so the two disagreed. Tests: an un-migrated nested value now renders its real key count instead of an empty editor, a sibling key that was never displayed survives the migrating write, and `replaceProperties` is asserted rather than assumed. All three fail against the previous read path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
APPROVE (self-review, mstomar125) Adversarial review focused on the risk in a diff that deletes ~360 lines: silently dropped behavior. It went through the old and new versions of both property forms field by field and confirmed the substitutions are faithful — date parse/ It found one major, fixed in Also addressed: the empty-enum guard this form used to carry is restored at the call site (not in the shared leaf, since the other two forms never had it and may legitimately clear a value), and the shared enum trigger humanizes an undeclared stored value so the control stops disagreeing with the collapsed header that already humanized it. Three new tests cover the migration path — real key count instead of an empty editor, a never-displayed sibling surviving the migrating write, and Known gap, non-blocking: no leaf-level test pins the date/number/enum Full suite 201 files / 4571 tests; lint + |
Summary
Follow-up to #2009. The recursive nested (object/array) property editor only reached
GenericSchemaForm, which persists flat atproperties[<field>]. The other two forms share the same field dispatch but write namespaced, so the flat modal couldn't be dropped in — it would have written nested edits to the wrong place, which is exactly why #2009 deferred them.What changed
The modal is now store-agnostic.
nested-property-modal.svelteno longer imports the store; it takes the currentvalueand anonPersistcallback from whichever form owns it. Each form supplies its own reader/writer, so all three share one editor and one modal while persisting to their own namespace:GenericSchemaFormproperties[<field>](flat — unchanged)TaskSchemaFormproperties.task[<field>]SchemaPropertyFormproperties[<nodeType>][<field>](through its existing flat→nested migration,titleTemplaterecompute andreplaceProperties: true)Both property forms now render leaves through the shared
SchemaFieldLeafinstead of their own copies of the five-case dispatch. That removes both fall-throughs in one step — the"Boolean field (not yet implemented)"stub and"Unknown field type"— and is most of the ~360 deleted lines. Only the assignee combobox stays hand-rolled, having no shared equivalent.Two behaviors were preserved deliberately: the enum default fallback (via a
leafValuehelper that resolves through the existinggetEnumValue), and close-the-popover-on-date-pick, which moved into the shared leaf — so it now also fixes the generic form and the nested editor, where the calendar previously stayed open.The summary button is shared too (
nested-field-trigger.svelte), so a nested field looks identical in every form.Tests
property-form-field-dispatch.test.tsis the crux: it renders each real form, clicks through to the modal, edits, and asserts the exact persistedpropertiesbag — proving each namespace and that sibling keys survive ({ task: { estimate: 3, address: { street: '1 Main' } } },{ invoice: { total: 10, address: … } }, flat for generic), plus boolean rendering/persistence. Also new: 3 modal-contract tests and 3nestedFieldSummaryunit tests. #2009's tests stay green.Full suite 4568 passed; lint +
svelte-checkclean.Closes #2027.
🤖 Generated with Claude Code