settings-hubrun.md01_define/output/spec.mdPlatform configuration is scattered across the admin surface of apps/web. Tenant settings live at
/settings, while locations (/admin/territories), services (/services), products (/products),
industries (/admin/industries), and the rest of the admin config (commercial, SLA, escalation,
integrations, statuses, setup checklist, demo data) are spread across three different sidebar
sections (Catalogue, Configure, and the footer) using inconsistent URL patterns. An admin has no
single place to configure the tenant, and the navigation is cluttered with one entry per area.
Worse, the settings keys themselves are non-deterministic. tenant_settings is a free-form
key/category/value collection (packages/services/src/db/models/tenant-setting.ts): keys are
typed ad-hoc through a modal with no validation beyond a character regex. Domain code reads hardcoded
keys — matching.minFitScore, commercial.vendorRevenuePercent, commercial.pricingRulesSet,
escalation.defaultEnabled — and silently falls back to a default if the row is missing, so settings
that should exist by default simply don't appear until someone creates them. The one seed that does
exist (SETTING_DEFAULTS in tenant-defaults.ts: default_currency, timezone,
sla_business_days_only) is orphaned — no code ever reads those keys. There is no canonical
registry, so an admin can't see what settings should exist, what they default to, or what they do.
This advances Refine the Bridge (dashboard usability and the small details of each interaction): a single, predictable configuration surface removes a recurring source of operator confusion and mis-configuration during vendor onboarding.
Two coordinated changes, both in apps/web and packages/services:
1. A canonical settings registry (the determinism fix). Introduce a single typed registry in
packages/services that declares every known tenant setting — key, category, value type,
default, and a human description. This registry becomes the source of truth:
getMinFitScore, getCommercialConfig, getEscalationConfig, etc.)
resolve their defaults from the registry instead of hardcoding them inline.seedTenantDefaults) seeds every registry entry create-if-missing, replacing
the short orphaned SETTING_DEFAULTS list, and backfills all existing tenants so every tenant has
every default-bearing setting present.2. A single tabbed settings hub (the consolidation). Build one consolidated admin screen with a tab per configuration area, reusing the existing manager/list components as tab panels:
/services, /products, /admin/industries, /admin/territories,
/admin/commercial, /admin/sla, /admin/escalation, /admin/integrations, /admin/status,
/setup-checklist, /admin/demo-data) redirect into their corresponding hub tab. Per-item
create/detail/edit routes (e.g. /services/[id]/edit) are preserved so deep links and forms keep
working.packages/services declaring every known setting's key,
category, type, default value, and description, and is exported for use by the app and seeds.tenant_settings remain visible and editable,
and are visually distinguished as not part of the registry.seedTenantDefaults seeds the full registry create-if-missing and backfills existing tenants,
so every tenant has every default-bearing setting present after the seed runs; already-set
values are never overwritten.apps/web, which
imports from @sustentus/ui).tenant_settings gains a registry layer./admin/status workflow itself (statuses/transitions remain version-controlled JSON changed via
PR); the hub only embeds the existing read-only viewer.apps/dashboards — this work is entirely in apps/web and packages/services.02_build/output/notes.mdpackages/servicesdb/services/tenant-setting/registry.ts (new): the canonical SETTINGS_REGISTRY
declaring every known setting's key, category, type, default, and
description, plus getSettingDefinition and seedableSettings helpers. This is
the single source of truth for what settings exist and what they default to.db/services/tenant-setting/index.ts: domain reads (getMinFitScore,
getEscalationConfig, getCommercialConfig) now resolve their defaults and
$setOnInsert descriptions from the registry instead of inline literals.
Effective values for already-configured tenants are unchanged.db/seed/tenant-defaults.ts: SETTING_DEFAULTS is now derived from
seedableSettings() (every registry entry whose default is non-null). The seed
already loops over all tenants create-if-missing, so existing tenants are
backfilled and never overwritten.db/services/index.ts: exports the registry + types via @sustentus/services/server.apps/web/app/(app)/admin/settingslayout.tsx (new): role-gated shell (admin, csm, sdm) with heading and a
role-filtered tab nav._components/tabs-config.ts (new): the tab catalogue + role visibility +
defaultTabForRole._components/settings-tabs.tsx (new): client tab nav (routed links, active state
from usePathname) — the active tab is encoded in the URL path.page.tsx: hub index now redirects to the first tab the role may see.general, locations, services, products, industries,
commercial, sla, escalation, integrations, statuses, setup,
demo-data — each reuses the existing feature component(s) for that area._components/settings-registry-manager.tsx (new) + general/page.tsx: the
Settings tab merges the registry with stored rows. Every registry setting always
appears (with its default when never edited); stored keys not in the registry are
shown and flagged "Unregistered". Edit creates-or-updates; registered rows offer
"reset to default", unregistered rows offer delete./services,
/products, /admin/industries, /admin/territories→locations, /admin/commercial,
/admin/sla, /admin/escalation, /integrations, /admin/status→statuses,
/setup-checklist→setup, /admin/demo-data. Per-item create/[id]/edit
routes are untouched and still work.lib/nav.ts: admin's scattered Catalogue/Configure/footer config entries collapse
to one "Settings" entry → /admin/settings; CSM/SDM's taxonomy links collapse to a
single "Configuration" entry → /admin/settings/services (the hub shows only the
tabs their role may see).lib/route-policies.ts: added shared-tab policies (/admin/settings/{services, products,industries,statuses} → admin/csm/sdm) before the admin-only
/admin/settings rule (first match wins).settings/page.tsx: the personal settings page drops the admin tenant-settings
section (now in the hub) and links admins to the hub; org profile, notification
preferences, and the matching section (admin/sdm) stay.admin/settings/layout.tsx + 12 tab pages.packages/services, exported — registry.ts.settings-registry-manager.tsx; defaults shown even when unedited.tenant-setting/index.ts.tenant-defaults.ts.nav.ts.pnpm typecheck was run locally and passed (9/9 tasks) given the size of
the change.matching.minFitScore now appears in the hub Settings tab (registry list) and the
matching section still lives on /settings (kept so SDM, who has no hub general
tab, retains it). Minor intentional overlap for admins.commercial.vendorRevenuePercent has a null default (absence is meaningful), so
it is listed but not seeded — matching the existing "presence means configured"
behaviour./admin/integrations (a separate tenant-integration config surface, not in the
sidebar) is left as-is; the hub Integrations tab and the /integrations redirect
use the catalogue list the admin sidebar actually pointed at.settings/_components/admin-tenant-settings-section.tsx and the older
tenant-settings-manager.tsx are now unused (superseded by the registry manager);
left in place to keep the diff focused.03_release/output/changelog.mdEverything you configure for your organisation now lives on a single settings screen with a tab for each area, instead of being scattered across the platform:
03_release/output/investor-update.mdWho it's for: Platform admins, plus CSM and SDM managers What shipped: A single tabbed settings hub, backed by a registry that guarantees every default setting always exists. Why it matters: A predictable, one-stop configuration surface — part of Refine the Bridge — cuts setup confusion during vendor onboarding.
New tenants now start with the full default settings set, not a blank slate.
Dig deeper: <merged-PR URL> · <changelog entry URL>
03_release/output/release.mdapps/docs/app/technical/development/database/page.mdx (seed now sources defaults from the registry)apps/docs/app/business/roles/page.mdx (admin settings hub + statuses tab location)apps/help/app/changelog/2026-06-18-settings-hub/page.mdx + investor draft in this PR/code-review run on the branch diff (correctness + removed-behavior + conventions finders).type over interface, named imports all satisfied; "sentence case" flags were false positives (single-word labels and "New setting"/"Edit setting" are already sentence case).settings-registry-manager.tsx swallowed action errors — added a resetError state so a failed reset/delete surfaces instead of silently closing the dialog.0/false defaults are handled (nullish, not falsy, checks), the AlertDialog reads current resetRow state, and non-admins can't reach admin-only tabs (tabs are role-filtered and middleware blocks /admin/settings/* admin paths)./admin/settings layout + 12 tab routes.packages/services, exported — tenant-setting/registry.ts.tenant-setting/index.ts.tenant-defaults.ts.nav.ts.