Summary
On a tabbed settings page built from YAML, saving one tab writes false to every boolean on every
other tab, and forces every repeater to '[]'. The user never sees the fields being reset, and
there is no error.
Verified against tangible/object v1.0.1.
Cause
Two behaviours that are each reasonable alone:
src/Settings/SettingsRenderer.php (~line 123) renders only the active tab's fields, so the
POST body contains only that tab's inputs:
// Render active tab content
foreach ($tabs as $tab) {
$tabKey = sanitize_key($tab['label']);
if ($currentTab !== $tabKey) {
continue;
}
src/DataView/RequestRouter.php::extract_post_data() (~line 446) iterates every field in the
schema, not the fields that were rendered:
foreach ( $this->config->field_configs as $name => $config ) {
$has_value = isset( $nested[ $name ] ) || isset( $params[ $name ] );
if ( ! $has_value ) {
if ( $this->registry->get_dataset_type( $type ) === DataSet::TYPE_BOOLEAN ) {
$data[ $name ] = false; // ← unchecked-checkbox handling
}
if ( $type === 'repeater' ) {
$data[ $name ] = '[]';
}
continue;
}
...
}
A checkbox on an inactive tab is absent from POST, which is indistinguishable from a box the user
deliberately unticked — so it is written false.
text fields are unaffected: they hit the bare continue, never enter $data, and
RequestHandler/SingularHandler.php::update() iterates $data only.
Steps to reproduce
- Define YAML settings with two tabs, each containing at least one
boolean field, both defaulting
to true.
- Save tab A.
- Read the option back — tab B's boolean is now
false.
Impact
Any safety-relevant checkbox becomes unsafe to put on a multi-tab settings page: an unrelated save
switches it off silently, and it is discovered much later, if ever. In one plugin this affected two
guards whose whole job was preventing a destructive operation, and the workaround was to demote both
to constants — losing the configurability the settings page existed to provide.
Suggested fix
Scope the missing-boolean/repeater defaults to fields that were actually rendered. RequestRouter
already has the layout, so it can restrict the loop to the submitted tab's field set. Alternatively
SettingsRenderer could emit hidden inputs carrying the current values for inactive tabs — cheaper,
but it makes the form carry state it does not display.
Happy to send a PR if the first approach is the preferred shape.
Summary
On a tabbed settings page built from YAML, saving one tab writes
falseto every boolean on everyother tab, and forces every
repeaterto'[]'. The user never sees the fields being reset, andthere is no error.
Verified against
tangible/objectv1.0.1.Cause
Two behaviours that are each reasonable alone:
src/Settings/SettingsRenderer.php(~line 123) renders only the active tab's fields, so thePOST body contains only that tab's inputs:
src/DataView/RequestRouter.php::extract_post_data()(~line 446) iterates every field in theschema, not the fields that were rendered:
A checkbox on an inactive tab is absent from POST, which is indistinguishable from a box the user
deliberately unticked — so it is written
false.textfields are unaffected: they hit the barecontinue, never enter$data, andRequestHandler/SingularHandler.php::update()iterates$dataonly.Steps to reproduce
booleanfield, both defaultingto
true.false.Impact
Any safety-relevant checkbox becomes unsafe to put on a multi-tab settings page: an unrelated save
switches it off silently, and it is discovered much later, if ever. In one plugin this affected two
guards whose whole job was preventing a destructive operation, and the workaround was to demote both
to constants — losing the configurability the settings page existed to provide.
Suggested fix
Scope the missing-boolean/repeater defaults to fields that were actually rendered.
RequestRouteralready has the layout, so it can restrict the loop to the submitted tab's field set. Alternatively
SettingsRenderercould emit hidden inputs carrying the current values for inactive tabs — cheaper,but it makes the form carry state it does not display.
Happy to send a PR if the first approach is the preferred shape.