descriptor` is the shape the * client's `shared/SchemaField` already consumes, and it makes "what is the default for key X" a * single lookup on the server — which matters, because defaults are resolved here and never on the * client. A bag written by an older build has no key for an option added since; if the client owned * the defaults, a save from a stale bundle would omit the key and the server would read nothing and * conclude "off" for a checkbox whose default is on. */ interface IntegrationOptionsInterface { /** * The options this connector offers, as fields the client can draw without knowing what they are. * * Field types are `shared/schemaTypes.ts`'s union: `text`, `textarea`, `number`, `checkbox`, * `select`, `user`, `owner`, `tags`, `pipeline`, `stage`. **Not `field`** — that one is a picker * of a record's own columns and needs a subject to enumerate, which a connector-level option does * not have. * * Every descriptor SHOULD carry a `default`. It is the value {@see options()} falls back to, and * an option with no declared default is one whose "off" state is whatever an empty string casts * to in the code that reads it. * * TWO OPTIONAL KEYS SHAPE THE SCREEN, AND BOTH ARE DATA RATHER THAN REACT. A connector with more * than a handful of options is a wall of controls otherwise — which is what WooCommerce's * fourteen were until 3.37.0. Neither key changes what is stored; a connector declaring neither * renders exactly as it always has. * * - **`heading`** — `array{label: string, help?: string}`. Read as *draw a heading before this * field*, not as *this field owns a group*: the client folds the schema into runs, a heading * opening one that continues until the next. That keeps the schema FLAT and KEYED, so * {@see options()}, {@see save_options()} and every `array_keys()` over it are untouched — the * alternative, nesting the array, would have rewritten all three for a change to a screen. * - **`showWhen`** — `array{field: string, not: array}`. The field is drawn only * while the named option's current value is NOT one of `not`. `field` MUST name a key in this * same schema: a typo hides its field for ever and says nothing, which is the failure mode that * put `expected_close` into an opportunity for a release. Test it. * * `showWhen` HIDES, IT DOES NOT EXCLUDE. A hidden field keeps its stored value and is still sent * on save, so switching a gate back on restores what was configured before rather than silently * resetting it. It is a statement about the screen, never about the bag. * * @return array, heading?: array{label: string, help?: string}, showWhen?: array{field: string, not: array}}> */ public function options_schema(): array; /** * The stored values, with every declared default filled in. * * Never contains a secret: secrets belong to {@see IntegrationInterface::settings_schema()} and * are handled by {@see Credentials}. Everything here is safe to send to the client as-is. * * @return array */ public function options(): array; /** * Store the options. * * @param array $input Values keyed as {@see options_schema()} declares. * * @return true|WP_Error An error message a human can act on, never a code alone. */ public function save_options( array $input ): bool|WP_Error; }