Skip to content

Store and teach settings in one shape at every scale - #35

Merged
ahegyes merged 9 commits into
trunkfrom
refactor/settings-at-every-scale
Nov 14, 2026
Merged

ahegyes merged 9 commits into
trunkfrom
refactor/settings-at-every-scale

Conversation

@ahegyes

@ahegyes ahegyes commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

Settings in the template now take the shape the framework's settings branch gave them, from its own tab to its recipes (X rulings 416–434).

  • Stored-form defaults, seeded once. Options::DEFAULTS holds every setting's default in the form its option stores, the greeting's message as an empty string. The first migration stores each absent default autoloaded in one loop, which a release that adds settings lists again, and each field's default reads its entry. A Unit row records a fingerprint of the key set each seeding step stores, and an Integration row holds each field's default to its entry (X ruling 418).
  • A fields class per feature. The greeting's fields live in src/Greeting/GreetingSettings.php; Settings keeps the tab's registration, the consent to delete data and the assembly (X ruling 419).
  • Number fields held by the framework. The greeting-orders validator goes: the framework holds the field to its min and step on WooCommerce's tab, and an emptied count stores empty and reads as the default 1, which the field shows as its placeholder (X rulings 420 and 431).
  • Recipes. New: adding a setting, where each kind of stored data lives, renaming a stored setting, a filter at a decision point, and retiring an @api function or constant a release shipped. Rewritten: the settings page and the without-WooCommerce steps on one option per field, the Payments-tab defaults in stored form, and the recurring jobs' repair gate as an hour-long transient (X rulings 416, 421–426, 432 and 433).
  • Migration steps name what they store as literals, so a later rename leaves a shipped step as it shipped; AGENTS.md's conventions 6 and 8 state it (X ruling 434).

"Update packages" moves the framework lock to its 8db8d53 split, which needs no change in the template.

Testing: Integration 91 tests on WordPress 7.1.4 with WooCommerce 11.3.0, the multisite group, the end-to-end suite, Unit, PHPCS, PHPStan, the PHP 7.4 parse, the scaffold rehearsal at the 28-character prefix and at dws_ao, and both audits.

The template locked the framework at its eb1473c split, before the
settings branch merged as 8db8d53: options pages that store each field
in its own option, number fields held to their min, max and step on
both hosts, and an uninstall consent read from its own option only.

The core, settings and woocommerce mirrors move to that split; the
bootstrap and shared mirrors are unchanged, and no other Composer or
npm package has a release to take beyond TypeScript 7, which stays
capped. The move forces no change in the template: it passes no
consent key, builds no options page, and its one number field still
carries its own validator.

Assisted-by: Claude Code:claude-opus-5-5
Options::DEFAULTS holds typed values for three of the four settings and
none for the greeting, so the seeding step converts each default by
hand, each field converts it again, and a setting added after the
first release needs seed lines of its own. WooCommerce also stores each
tab field's default whenever its own version changes, so a field
default that differs from the seeded one wins or loses by which write
runs first.

These rows expect the defaults in the form each option stores: the
first migration stores each DEFAULTS entry as it stands, the
greeting's as an empty string, each autoloaded, and the row fails
until its option list names every key; each field's default equals its
DEFAULTS entry; and a Unit row records a fingerprint of the key set
each seeding step stores, failing when the keys change without a step
and fingerprint for them. The fingerprint counts each key after the
plugin's prefix, which the fill-in renames. The readers' default rows
assert the values they read.

Assisted-by: Claude Code:claude-opus-5-5
DEFAULTS held typed values that the seeding step and each field
converted by hand, and no entry for the greeting, so a setting added
later needed seed lines of its own and a field default could drift from
the seeded one.

DEFAULTS now holds an entry for every option in the form the option
stores it, an empty string standing in for the greeting's translated
default. The first migration primes the rows and adds each absent
default autoloaded in one loop, which a release that adds settings
lists again; each field's default reads its DEFAULTS entry; and the
boolean and integer readers fall back to their default through the same
filter that reads the stored value.

Assisted-by: Claude Code:claude-opus-5-5
The greeting's order-count field carries a validator of its own, which
rejects an empty submission and words its own message, while the
framework now holds a number field to the min and step it declares on
both hosts and treats an emptied number as cleared.

These rows expect the framework's rules on the field: a count below 1,
off the step of 1, not a number or too large to check keeps the stored
count and names the bound it missed; an emptied count stores an empty
string, reads as the default 1 and shows that default as the field's
placeholder (X ruling 431), in the suite and in the browser. The row
that called the field's validator directly goes with it.

Assisted-by: Claude Code:claude-opus-5-5
The order-count field's validator duplicated what the framework's
number rules now do for any number field without one: hold it to the
min and step in its custom attributes and store the canonical digits.
It also rejected an emptied field, which the framework stores as
cleared.

The validator goes, so the field's min of 1 and step of 1 decide on
the WooCommerce tab, and the field shows its default as its placeholder,
so an emptied count, which reads as that default, shows it (X ruling
431).

Assisted-by: Claude Code:claude-opus-5-5
Settings held every field array of the tab, so a plugin with many
features would grow one class by every feature's fields and validators,
and split it only once it had a second section, a point at which its
code would restructure.

The greeting's title, toggle, message and order count move to
GreetingSettings in the greeting's folder, a final class whose public
static get_settings_fields() returns them; Settings keeps the tab's
registration, the plugin-level consent to delete data, and the
assembly, which spreads the greeting's fields first. The tab renders
and saves as before.

Assisted-by: Claude Code:claude-opus-5-5
AGENTS.md, the architecture document, the scaffold README and the tests
README still described typed defaults that the first step converted, a
settings tab holding the greeting's fields and validator, and an end-to-
end test that rejects an emptied count.

They now say that each setting is one autoloaded option whose stored-
form default the first migration seeds and its field reads, that a
feature's fields live in its fields class, which Settings spreads into
the tab, that removing the greeting replaces the seeding fingerprint,
and what the suites now cover.

Assisted-by: Claude Code:claude-opus-5-5
The settings-page and without-WooCommerce recipes still stored a page's
fields in one array option, with a second family of keys, defaults and
readers and a consent key the framework no longer accepts; the
recurring-jobs recipe kept its repair gate among the settings, which the
seeding loop would now store and fingerprint; and no recipe said how to
add, rename or place a stored value, how other code changes a decision,
or how a released @api symbol retires.

The recipes now cover:
- a setting: its key and stored-form default on Options, its field in
  its feature's fields class, which Settings spreads beside its own
  plugin-level fields, its reader, and the seeding step and
  fingerprint its release appends (X rulings 418, 419 and 425);
- where stored data lives: settings, per-object overrides, list
  settings, entities, runtime state through OptionRows, and transients
  for markers that only spare repeated work (X rulings 422, 426, 433);
- renaming a stored setting in a major release: a plain step appended
  after the steps earlier releases shipped and before the release's own
  batched and seeding steps, the old row winning, and the names a
  restricting setting keeps (X ruling 424);
- the settings page on one option per field, with tabs and fields
  given apart, no validator and the consent kept under its own option
  in the without-WooCommerce steps (X rulings 416 and 420);
- stored-form defaults on the Payments-tab fields;
- the recurring jobs' repair gate as an hour-long transient named on
  the class that ensures the jobs (X ruling 433);
- a filter at a decision point, carrying the object it decides for,
  instead of a filter on every settings read (X rulings 421 and 422);
- retiring an @api function or constant a release shipped, and
  deleting one no release has (X ruling 432).

Assisted-by: Claude Code:claude-opus-5-5
A step that names a setting through its Options constant keeps its code
but not its behavior: once a later major renames the setting, the
shipped step acts on the new name, so a site updating from before that
step runs it against the new, still-empty row, and the rename's copy
then brings back the value the step never converted.

AGENTS.md's migrations convention now has every step name each stored
name it touches, such as an option or meta key or a scheduled hook, and
each default as a literal and read the stored rows directly, never
through Options or another class's constant, with the seeding step,
which follows Options::DEFAULTS by design, as the one exception (X
ruling 434). The options convention excepts the migration steps from
reading options only through Options, the architecture document says
the components read them that way, and the rename recipe's example
step names both options as literals.

Assisted-by: Claude Code:claude-opus-5-5
@ahegyes ahegyes changed the title Refactor/settings at every scale Store and teach settings in one shape at every scale Nov 14, 2026
@ahegyes
ahegyes merged commit ab0ffa9 into trunk Nov 14, 2026
31 checks passed
@ahegyes
ahegyes deleted the refactor/settings-at-every-scale branch November 14, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant