writableFields function
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;
}
}
}