FF-EPIC-17 and FF-EPIC-18 deliver a complete configuration backend reachable only by
curl. A config service with no editor delivers none of its value — its entire purpose is to
let non-engineers change settings. These frames freeze that console: a settings editor that says
where every value came from, a key catalog that says what every key is, and the
secret and audit surfaces that make changes safe and attributable.
The hard part here is not the inputs. It is communicating why a setting has its value and
why some settings cannot be changed — invisible in a plain form. Walk each frame; approve
per flow.
Scope switcher, namespace, typed inputs per valueType, and a provenance badge on every
row: set here / inherited from / locked by. Atomic save with expectedVersion.
Six entry shapes: set-here, inherited, locked-by-ancestor naming the ancestor, readonly,
scope-not-allowed, default — plus unset vs pin-the-parent's-value as two
distinct operations.
Loading, empty namespace, load error, read-only scope (fail-closed, no save bar), and system-hidden absent entirely — omitted by the API, never filtered client-side.
VERSION_CONFLICT → re-read and merge (never blind retry, no force-save),
per-field VALIDATION_ERROR, LOCKED_BY_ANCESTOR mid-edit, and
stale value vs updated definition.
Every KeyDefinition in a namespace: type, allowed scopes, system / hidden / secret /
readonly / deprecated flags, defaults. The only surface where hidden keys are visible.
One definition plus the full resolution chain — which scope wins and why, the no-divergence case, and a chain terminated by a lock with the scopes beneath marked unreachable.
Loading, no namespaces vs namespace with no keys vs no search results (three different meanings), error, and non-platform-admin refused before any data loads.
Write-only: isSet not value, no show-toggle, replace-only,
reveal-once (separately authorized and audited), SECRET_UNAVAILABLE ≠ unset, and
may-set-but-not-reveal.
Append-only trail with actor, scope and reason. Revert appends, never rewinds. Secret history redacted both sides. Empty / loading / error, and error ≠ forbidden.
Approving a flow approves its component/package plan and its contract binding —
implementation cannot quietly invent a different architecture. Mirrored in
manifest.json → build. Approval is per flow: the editor (Flow 1) can
be approved and built while the catalog (Flow 2) and the secret/audit surfaces (Flow 3) still iterate.
ConfigSettingsEditorFlowConfigConsoleShellScopeSwitcherScopeChainStepNamespacePicker
SettingsEditorFormSettingCategoryGroupSettingRow
TypedValueInputEnumValueSelectBooleanValueToggle
NumberValueInputDurationValueInputJsonValueEditor
UrlValueInputColorValueInput
ProvenanceBadgeLockedByAncestorNoticeInheritedFromNotice
ResetValueMenuUnsetOperationOptionPinParentValueOption
RequiresRestartNoticeStaleValueWarningScopeNotAllowedNotice
ReadOnlyScopeNoticeSaveBarDirtyChangeCounterChangeReasonField
FieldValidationErrorVersionConflictResolverConflictRow
ConfigSkeletonEmptyNamespaceStateConfigLoadError
ConfigKeyCatalogFlowKeyCatalogTableKeyCatalogFiltersKeyCatalogSearchKeyDefinitionRow
ValueTypeBadgeAllowedScopesListKeyFlagBadges
KeyDefinitionDetailDefinitionFactsTable
ResolutionChainViewResolutionChainStepNoDivergenceNotice
LockTerminatedChainNoticeCatalogPagination
EmptyNamespacesStateEmptyKeysStateNoCatalogResultsState
PlatformAdminOnlyNotice
ConfigSecretAuditFlowSecretValueFieldSecretSetIndicatorSecretReplaceInput
RevealSecretDialogRevealOnceValueSecretUnavailableNotice
AuditHistoryTableAuditEntryRowAuditOperationBadge
RedactedValueCellAuditActorCell
RevertToValueDialogRevertSemanticsNotice
EmptyHistoryStateHistoryLoadError
@fuzefront/config-ui (new)@fuzefront/config-client (existing — consumed, frozen by FF-EPIC-17-S1)@fuzefront/design-system (existing — composed from)DataTable — catalog and history tablesStatusPill — provenance, flags, operationsSearchInput — debounced search fieldSwitch — boolean settingsSkeleton — loading statesBanner — error / warn / info / ok blocksDialog — reveal, revert, conflict resolutionBreadcrumb — the scope chainCodeField — JSON / duration monospace input
Design system: @fuzefront/design-system (fuse-seam, tokens only) · Epic: FF-EPIC-19
(Configuration Management UI) · Story: FF-EPIC-19-S1 / FFRNT-280.
Contracts. Flows 1 and 2 bind to the real, frozen
services/config-service/openapi.yaml via @fuzefront/config-client —
GET /v1/namespaces, GET /v1/namespaces/{ns}/keys,
GET /v1/namespaces/{ns}/keys/{key}, GET /v1/config,
PUT /v1/config. Flow 3 is partly anticipated: secret reveal and the
history / revert surfaces are FF-EPIC-18-S1/S2/S3 and are not in the frozen contract today —
these frames commission them, and bind revert to the existing
set/unset operations rather than inventing a delete.
Provenance is the UI. Every row renders source, locked,
lockedBy and editable — a build that reads only value produces a
form that looks right and misrepresents its own contents.
Hidden means absent. The editor performs no client-side filtering and must have no code path that
could render an isHidden key. QA asserts on the network payload, not on the absence of a
rendered row.
The UI is not the authorization boundary. A disabled input reflects editable: false;
the server refuses the write regardless. The four reasons a control is disabled — locked, readonly,
scope-not-allowed, no Permit grant — are visually distinct, because the user's next action differs.
DS primitives listed above are not all in @fuzefront/design-system today.
frontend-engineer (sole editor of design-system/) adds them as foundation PRs;
these frames compose them from tokens only in the meantime.
Approve per-flow by setting approved: true on the flow in manifest.json,
or reply @claude approve <flow> / @claude reject: <reason>.