feat(17-01): repeater field type and the dependent dropdown save recheck

- cabana: type repeater with nested form.fields (text, textarea, number,
  checkbox, switch, dropdown, richeditor), prompt, minItems, maxItems,
  titleFrom; groups, style, nested repeaters and other types fail boot
- rows decoded before projection: only nested keys, coerced scalars,
  per-row required (field.index.nested), row bounds; ProjectWritableFields
  exempts repeater lists only, nestedValue unchanged
- nested richeditor values sanitized with notices under the row path
- dependent dropdowns rechecked on save, top level and per row; null when
  the sibling takes no choice, 422 form.dependent_invalid otherwise
- dependent-options accepts repeater.field; schema carries RepeaterForm
- backend lang repeater.max_items, repeater.min_items,
  form.dependent_invalid; TestCompanionsRepeaterSmoke; docs
This commit is contained in:
2026-10-12 15:24:01 +02:00
parent 71fa597830
commit 46835fd064
16 changed files with 961 additions and 43 deletions

View File

@@ -437,7 +437,9 @@ func constrainFileID(g pact.Router) {
func constrainNested(g pact.Router) {
constrainController(g)
g.Where("segment", "[A-Za-z_][A-Za-z0-9_]*")
// segment admits one dot for the repeater.nested path of a dependent
// dropdown inside a repeater row (fields/{field}/dependent-options).
g.Where("segment", `[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?`)
// name admits a hyphen for the literal dependent-options segment; a
// relation or file field name with one matches nothing and answers 404.
g.Where("name", "[A-Za-z_][A-Za-z0-9_-]*")