From 33f1db759721532cef3f73c5622e0edbc36100e9 Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Mon, 15 Jun 2026 06:30:33 +0300 Subject: [PATCH] fix(frontend): rebind server-edition settings to server_edition.* key (MCP-1087) MCP-1086 (backend rename teams->server_edition) has now landed. Flip the frontend settings binding from the legacy teams.* dot-paths to the canonical server_edition.* paths, and add aliasServerEdition() in Settings.vue so old configs that still carry the teams key still hydrate the form. - settings/fields.ts: teams.{enabled,oauth.provider,max_user_servers} -> server_edition.{...} - Settings.vue: hasTeams -> hasServerEdition computed (gates on server_edition != null || teams != null); aliasServerEdition() applied at loadConfig so legacy configs hydrate without a tab-disappear regression - Unit test: update assertion from ^teams\. to ^server_edition\. now that the backend contract has landed Verified: vitest 142/142, make build. Co-Authored-By: Paperclip --- frontend/src/views/Settings.vue | 31 +++++++++++++++---- frontend/src/views/settings/fields.ts | 17 +++++----- .../settings-server-edition-wording.spec.ts | 16 +++++----- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index c7008bdc..b7b77375 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -120,7 +120,7 @@ -
+

{{ serverEditionTitle }}

@@ -212,7 +212,12 @@ const loadError = ref('') const activeTab = ref('security') const showConnect = ref(false) const state = reactive<{ working: any; original: any }>({ working: {}, original: {} }) -const hasTeams = computed(() => state.working && state.working.teams != null) +// Server-edition (multi-user) config lives under `server_edition` (MCP-1086). +// Gate on the canonical key, falling back to the legacy `teams` key so a config +// written before the rename still surfaces the Server Edition tab. +const hasServerEdition = computed( + () => state.working && (state.working.server_edition != null || state.working.teams != null) +) // cross-section search: type to find any setting across all tabs const search = ref('') @@ -220,7 +225,7 @@ const allFields = computed(() => [ ...securityFields, ...generalFields, ...advancedAccordions.flatMap((a) => a.fields), - ...(hasTeams.value ? serverEditionFields : []), + ...(hasServerEdition.value ? serverEditionFields : []), ]) const filteredFields = computed(() => { const q = search.value.trim().toLowerCase() @@ -255,7 +260,7 @@ const tabs = computed(() => { { id: 'general', label: 'General', icon: '⚙️' }, { id: 'advanced', label: 'Advanced', icon: '🧰' }, ] as Array<{ id: string; label: string; icon: string }> - if (hasTeams.value) base.push({ id: 'teams', label: SERVER_EDITION_TAB_LABEL, icon: '👥' }) + if (hasServerEdition.value) base.push({ id: 'teams', label: SERVER_EDITION_TAB_LABEL, icon: '👥' }) base.push({ id: 'raw', label: 'Raw JSON', icon: '{ }' }) return base }) @@ -282,6 +287,16 @@ function clone(v: T): T { return JSON.parse(JSON.stringify(v)) } +// Back-compat for the teams -> server_edition rename (MCP-1086): if a config +// only carries the legacy `teams` key, mirror it onto `server_edition` so the +// form (which binds to `server_edition.*`) hydrates. Mutates and returns cfg. +function aliasServerEdition(cfg: any): any { + if (cfg && cfg.server_edition == null && cfg.teams != null) { + cfg.server_edition = cfg.teams + } + return cfg +} + async function loadConfig() { loading.value = true loadError.value = '' @@ -289,8 +304,12 @@ async function loadConfig() { const response = await api.getConfig() if (response.success && response.data) { const cfg = response.data.config - state.working = clone(cfg) - state.original = clone(cfg) + // The server-edition form binds to `server_edition.*` (MCP-1086). The + // backend loader already normalizes a legacy `teams` key to + // `server_edition`, but alias it here too so a config still carrying the + // old key hydrates the form (edits always save under `server_edition`). + state.working = aliasServerEdition(clone(cfg)) + state.original = aliasServerEdition(clone(cfg)) configJson.value = JSON.stringify(cfg, null, 2) configStatus.value = { valid: true } loaded.value = true diff --git a/frontend/src/views/settings/fields.ts b/frontend/src/views/settings/fields.ts index 769799bc..715a1345 100644 --- a/frontend/src/views/settings/fields.ts +++ b/frontend/src/views/settings/fields.ts @@ -262,17 +262,18 @@ export const GENERAL_FIELDS: SettingField[] = [ ] // ---- Server edition (multi-user) section ---- -// User-facing wording is "Server Edition" (MCP-1087). The config dot-paths -// deliberately stay on the legacy `teams.*` key: the backend rename of the -// top-level config key (`teams` -> `server_edition`, MCP-1085 / PR #607) is -// not merged, so a live config is still `teams`-keyed. Flip these to -// `server_edition.*` in the follow-up only once that backend change lands. +// User-facing wording is "Server Edition" (MCP-1087). The backend rename of the +// top-level config key (`teams` -> `server_edition`, MCP-1086) has landed, so +// these dot-paths write/read the canonical `server_edition.*` key. Legacy +// `teams`-keyed configs still populate the form: the backend loader normalizes +// `teams` -> `server_edition` on load, and Settings.vue aliases it defensively +// so old configs hydrate the form while edits always save under `server_edition`. export const SERVER_EDITION_TAB_LABEL = 'Server Edition' export const SERVER_EDITION_SECTION_TITLE = '👥 Server Edition' export const SERVER_EDITION_FIELDS: SettingField[] = [ - { key: 'teams.enabled', label: 'Enable multi-user mode', control: 'toggle', restart: true }, - { key: 'teams.oauth.provider', label: 'OAuth provider', control: 'select', options: ['', 'google', 'github', 'microsoft'].map((v) => ({ value: v, label: v || '(none)' })) }, - { key: 'teams.max_user_servers', label: 'Max servers per user', control: 'number', min: 0 }, + { key: 'server_edition.enabled', label: 'Enable multi-user mode', control: 'toggle', restart: true }, + { key: 'server_edition.oauth.provider', label: 'OAuth provider', control: 'select', options: ['', 'google', 'github', 'microsoft'].map((v) => ({ value: v, label: v || '(none)' })) }, + { key: 'server_edition.max_user_servers', label: 'Max servers per user', control: 'number', min: 0 }, ] // ---- Section 3: Advanced (subsystem accordions) ---- diff --git a/frontend/tests/unit/settings-server-edition-wording.spec.ts b/frontend/tests/unit/settings-server-edition-wording.spec.ts index dcbcaad3..dfb31c90 100644 --- a/frontend/tests/unit/settings-server-edition-wording.spec.ts +++ b/frontend/tests/unit/settings-server-edition-wording.spec.ts @@ -5,13 +5,11 @@ import { SERVER_EDITION_FIELDS, } from '../../src/views/settings/fields' -// MCP-1087: the Settings server-edition surface must read "Server Edition", -// not the legacy "Teams" wording. The config *keys*, however, deliberately -// stay on the legacy `teams.*` dot-paths until the backend rename of the -// config key (`teams` -> `server_edition`, MCP-1085 / PR #607, currently -// unmerged) lands. Flipping the keys early would make `hasTeams` read -// `state.working.server_edition`, which a live `teams`-keyed config doesn't -// have -> the whole server-edition tab silently disappears. +// MCP-1087 + MCP-1086: the Settings server-edition surface must read "Server +// Edition" (not "Teams"), and the config dot-paths must target the canonical +// `server_edition.*` key now that MCP-1086 (backend rename) has landed. +// Legacy `teams`-keyed configs still hydrate via aliasServerEdition() in +// Settings.vue + the backend loader alias. describe('Settings server-edition wording (MCP-1087)', () => { it('uses "Server Edition" wording with no "Teams" left in user-facing labels', () => { expect(SERVER_EDITION_TAB_LABEL).toBe('Server Edition') @@ -20,10 +18,10 @@ describe('Settings server-edition wording (MCP-1087)', () => { expect(SERVER_EDITION_SECTION_TITLE).toMatch(/Server Edition/) }) - it('keeps the config field keys on the legacy `teams.*` contract (backend rename pending)', () => { + it('uses the canonical server_edition.* config dot-paths (MCP-1086 backend rename landed)', () => { expect(SERVER_EDITION_FIELDS.length).toBeGreaterThan(0) for (const f of SERVER_EDITION_FIELDS) { - expect(f.key).toMatch(/^teams\./) + expect(f.key).toMatch(/^server_edition\./) } }) })