OPS-012
A settings page an operator can actually use
Prefix-based grouping dumps most keys into an alphabetical wall. A catalogue with tabs, help text and a search — plus a test that fails the build when a migration adds an uncatalogued key.
Settings pages rot. They start grouped by key prefix, which works while there are twenty keys, and by the time there are a hundred most of them have landed in a category called Other, sorted alphabetically, with nothing explaining what any of them do.
A catalogue inverts it: every key is explicitly assigned a tab, a plain-language label and a line of help. Order comes from the catalogue, not the alphabet.
The part that keeps it true is a test. If a migration adds a setting and nobody catalogues it, the build fails — which is the only reliable way to stop the Other tab coming back.
/** Every key, deliberately placed. The raw key is shown small underneath. */
function settings_catalogue(): array
{
return [
'site_name' => ['Site', 'Site name', 'Shown in the masthead and page titles.'],
'base_url' => ['Site', 'Base URL', 'No trailing slash. Used for links in email.'],
'claim_cooldown' => ['Economy', 'Claim cooldown (minutes)', 'How long between claims for one user.'],
'reward_units' => ['Economy', 'Reward per claim', 'In the coin smallest unit.'],
'withdraw_min' => ['Payouts', 'Minimum withdrawal', 'Below this, the withdraw form refuses.'],
'fp_send_key' => ['Payouts', 'FaucetPay send key', 'Scoped key. Leave blank to pay by hand.'],
'turnstile_site' => ['Security', 'Turnstile site key', 'Blank disables the captcha entirely.'],
// …every key, with no exceptions
];
}
/** tests/settings.php — fails the build when the catalogue drifts. */
function test_settings_catalogue(PDO $pdo): array
{
$fail = [];
$cat = settings_catalogue();
$keys = $pdo->query('SELECT k FROM settings')->fetchAll(PDO::FETCH_COLUMN);
foreach ($keys as $k) {
if (!isset($cat[$k])) {
$fail[] = "setting '$k' exists but is not in the catalogue";
}
}
foreach ($cat as $k => [$tab, $label, $help]) {
if (!in_array($k, $keys, true)) {
$fail[] = "catalogue lists '$k' which no longer exists";
}
if (trim($help) === '') {
$fail[] = "setting '$k' has no help text";
}
if ($tab === 'Other') {
$fail[] = "setting '$k' landed in Other — give it a real tab";
}
}
return $fail;
}
Using it
Put a search box above the tabs that filters across all of them at once. Not knowing which tab a setting is on is the actual problem; search solves it directly.
Move machine-written keys — cron heartbeats, internal version markers — to a read-only table. They are not settings.
Show the raw key under each label. It is what appears in documentation and support threads.
What bites people
A label that just restates the key ("Site name" for site_name) is fine. Do not contort labels to satisfy an over-strict test; relax the test instead.
Blank password and key fields should leave the stored value alone rather than clearing it. Say so in the help text.
Deleting a setting in a migration without removing it from the catalogue fails the build — which is the test working, not a problem.