writableFields function

Iterable<SchemaComponent> writableFields(
  1. List<SchemaComponent> components
)

Writable leaves reachable from components — the payload-side mirror of RuleExtractor::childrenOf() on the Laravel side. A hidden or disabled container gates its entire subtree before recursion, not only the leaves inside it: Filament lets a form hide or disable a whole Section, and every field inside inherits that rather than needing its own flag. Checking only leaves (the original bug here) let a field nested under a hidden or disabled container survive into the payload because its own hidden/disabled defaulted to false.

writable gates the same way: false means the server cannot persist this field at all (a limit of this package, not a UI decision), so submitting it is a control whose contents are discarded behind a 200.

Top-level and shared, not private to FormValues: client_validator.dart needs the exact same set of fields — a rule on a field this walk would exclude from the payload can only produce an error the user can neither see nor fix. Task 7 shipped a Critical from this bug class once already; two textually-parallel private copies of this descent are not a guarantee that survives editing, so there is exactly one now.

Implementation

Iterable<SchemaComponent> writableFields(
  List<SchemaComponent> components,
) sync* {
  for (final component in components) {
    if (component.hidden || component.disabled || !component.writable) {
      continue;
    }
    switch (component) {
      case LayoutComponent(:final children):
        yield* writableFields(children);
      case UnknownComponent(:final children):
        yield* writableFields(children);
      case FileComponent(:final readOnly) when readOnly:
        continue;
      // A repeater is one writable field, its own name — never its item
      // template's field names — the client-side mirror of `WritableNames`
      // contributing only the repeater's own key. A readOnly repeater (a
      // relationship, or an older server that never published config) has
      // the same nowhere-to-go problem a readOnly file field does.
      case RepeaterComponent(:final readOnly) when readOnly:
        continue;
      default:
        if (component.name != null) yield component;
    }
  }
}